Skip to content

Comments

THREESCALE-8916: Make strong passwords mandatory#4195

Open
jlledom wants to merge 28 commits into3scale:masterfrom
jlledom:THREESCALE-8916-admin-strong-paswords
Open

THREESCALE-8916: Make strong passwords mandatory#4195
jlledom wants to merge 28 commits into3scale:masterfrom
jlledom:THREESCALE-8916-admin-strong-paswords

Conversation

@jlledom
Copy link
Contributor

@jlledom jlledom commented Jan 9, 2026

Note
This PR includes all changes from #4194. To make it easier to review, jlledom#3 includes only the actual diff to make strong passwords mandatory.

What this PR does / why we need it:

Currently, a provider can enforce strong password for the developer portal, and weak passwords are accepted by default. About admin portal, there's no option to enable strong passwords, weak passwords are always accepted.

I think both situations make no sense. I don't think it's acceptable to allow users decide whether they enforce strong passwords or not, as long as strong passwords are possible, that should be the default. And same thing about admin portal.

After a discussion via slack, we agreed on this terms:

  1. Enforce NIST policies
  2. Enforce for all users with password, not matter how did they sign up
    • Currently, users created automatically are not affected by the strong passwords setting. This is problematic because master, providers and buyers admin users are created this way, so those are never enforced to have strong passwords no matter the setting.
  3. Unify UI and API behavior
    • API endpoints don't consistently check for strong passwords, after this PR, all endpoints ask for strong passwords, also all UI forms.
  4. Remove the strong passwords setting
    • It will continue in db but completely ignored, since its mandatory now.
  5. Add a new setting on settings.yml to allow weak passwords for us
    • For develop and test purposes, we can add strong_passwords_disabled: true to settings.yml. If not set at all, strong passwords are enabled, so clients don't have to change anything on their side.
  6. Fix javascript password validation in forms, it's very inconsistent now
  7. db:seed should accept weak passwords
    • This is possible without changes in the code, just ensure to set strong_passwords_disabled to true before running the task
  8. John Doe: allow 123456
    • I added a new signup type called :smaple_data. Sample data accepts weak passwords, no matter the setting.

This affects multiple screens, but also API endpoints, this is the complete list:

  • All scenarios with a password
    • Admin portal
      • UI
        • User invitation signup form
        • Personal details
        • Edit user
        • Forgot password
        • Create new buyer
        • Edit buyer user
      • API
        • POST /master/api/providers.xml
        • POST /admin/api/users.xml
        • PUT /admin/api/users/{id}.xml
        • POST /partners/providers
        • POST /partners/providers/{id}/users
    • Developer portal
      • UI
        • New buyer signup form
        • Existing buyer user invitation signup form
        • Personal details
        • Edit user
        • Forgot password
      • API
        • POST /admin/api/signup.xml
        • POST /admin/api/accounts/{id}/users.xml
        • PUT /admin/api/accounts/{id}/users/{id}.xml

Additionally, I also added some behavioral changes to the UI. To solve bugs or behaviors I think are incorrect. This is the summarty Claude generated:

Screens/Forms Modified

Screen/File Location Changes
React Password Validation (app/javascript/src/Login/utils/validations.ts) Frontend JS Added strong password validation to React forms. Changed from length: { minimum: 6 } to regex pattern matching backend rules
(16+ chars, ASCII printable only). Added STRONG_PASSWORD_* constants mirroring backend.
Create Buyer Account (app/views/buyers/accounts/new.html.erb) Admin Portal → Audience → Accounts → Create Added type: 'password' to password field. Previously the field was showing password as clear text.
Edit Buyer User (app/views/buyers/users/edit.html.erb) Admin Portal → Audience → Accounts → [Account] → Users → [User] → Edit Changed password and password_confirmation fields from required: true to `required:
false`. Password is now optional when editing a buyer user.
Edit Provider User (app/views/provider/admin/account/users/_form.html.erb) Admin Portal → Account Settings → Users → [User] → Edit Changed password and password_confirmation fields from required: true to
required: false. Password is now optional when editing a provider user.
Edit Personal Details (app/views/provider/admin/user/personal_details/edit.html.slim) Admin Portal → Account Settings → Personal Details Multiple changes:
1. Removed the toast/alert prompting SSO users to set password via reset form
2. Password field now always visible (not conditional on using_password)
3. Password field changed to required: false
4. Added type: 'password' to hide password input
5. "Current password" section only shown when using_password? is true (users with existing password must confirm it)
Locales (config/locales/en.yml) N/A Fixed YAML syntax for error message. Removed set_password_html translation (toast was removed).

Summary of behavioral changes

  1. Strong password validation in React - Frontend now enforces the same 16+ character ASCII-printable password rules as the backend.

  2. Password fields show as masked - Fixed forms that were accidentally displaying passwords as plain text.

  3. Password fields are optional on edit - When editing existing users (buyer or provider), password is no longer required. You can update other fields without changing the password.

  4. Password field always visible - On personal details page, the password field is now always shown regardless of how the user signed up (SSO, password, etc.). Any user can set/change their password.

  5. Current password only required when applicable - The "Current password" confirmation field only appears when the user already has a password set (using_password?). New SSO users setting a password for the first time
    don't need to provide a current password.

  6. Removed SSO password reset toast - SSO users no longer see the banner telling them to use the password reset form. They can now set their password directly from the personal details form.

  7. Works with enforce SSO - Passwords can still be set/changed even when enforce SSO is enabled.

I know this is a though one. I recommend to review the commits one by one. Also, I'll add some comments to provider further explanations.

Which issue(s) this PR fixes

https://issues.redhat.com/browse/THREESCALE-8916
https://issues.redhat.com/browse/THREESCALE-11548

Verification steps

You can go through any (ideally all) screens above an try to set a weak password. Also tests should pass.

List of added tests

This is the complete list of tests added in this PR. To cover all the new behaviour explained above:

File Description
test/unit/authentication/by_password_test.rb Added tests for validate_password?, validate_strong_password?, and using_password? methods. Covers scenarios for different signup types (new_signup, minimal, sample_data, machine), password change detection, and strong password validation bypass for sample_data users.
test/unit/liquid/drops/user_drop_test.rb Added tests for using_password? and password_required? Liquid drop methods, verifying correct behavior for SSO users, by_user signups, and sample_data signups.
test/unit/user/signup_type_test.rb Added tests for sample_data?, machine?, and by_user? methods to verify sample_data signup type is correctly identified and classified as a machine signup.
test/unit/user/states_test.rb Added ActivateOnMinimalOrSampleDataTest class with tests for auto-activation logic, covering minimal and sample_data signups with/without password and approval requirements.
test/unit/logic/provider_signup_test.rb New test file verifying John Doe (sample data user) is created with sample_data signup type, bypasses strong password validation, and is auto-activated.
test/integration/developer_portal/signup_test.rb Added StrongPasswordsTest class testing developer portal signup with strong password validation enabled/disabled.
test/integration/provider/signups_controller_integration_test.rb Added SignupsControllerStrongPasswordsTest class testing admin portal provider signup with strong password validation enabled/disabled.
test/integration/provider/admin/account/users_controller_test.rb Added tests verifying password fields are always visible on provider user edit page, including for SSO users without password.
test/functional/buyers/users_controller_test.rb Added EditPagePasswordFieldsTest class verifying password fields are always visible on buyer user edit page, including for SSO users.
test/functional/provider/admin/user/personal_details_controller_test.rb Added SSOUserWithoutPasswordTest, UserWithPasswordTest, and EnforceSSOEnabledTest classes testing personal details page behavior for SSO users, current password requirements, and enforce SSO scenarios.
test/functional/partners/providers_controller_test.rb Added tests for creating providers without password and strong password validation on Partners API.
test/functional/partners/users_controller_test.rb Added tests for creating users with/without password, error handling (422 status), and strong password validation on Partners API.
test/integration/admin/api/buyers_users_controller_test.rb Added tests for updating buyer users with password via API and strong password validation.
test/integration/user-management-api/users_test.rb Added tests for strong password validation when updating provider users via PUT /admin/api/users/{id} endpoint.

@jlledom jlledom changed the title Threescale 8916 admin strong paswords THREESCALE-8916: Make strong passwords mandatory Jan 9, 2026
@jlledom jlledom force-pushed the THREESCALE-8916-admin-strong-paswords branch from da18ea7 to 5b00c7f Compare January 9, 2026 14:14
Comment on lines 8 to 17
const RE_STRONG_PASSWORD = new RegExp(
'^' +
'(?=.*\\d)' + // at least one digit
'(?=.*[a-z])' + // at least one lowercase
'(?=.*[A-Z])' + // at least one uppercase
`(?=.*[${STRONG_PASSWORD_SPECIAL_CHARS.replace(/[[\]\\]/g, '\\$&')}])` + // at least one special char
'(?!.*\\s)' + // no whitespace
`.{${STRONG_PASSWORD_MIN_SIZE},}` + // minimum length
'$'
)
Copy link
Contributor

Choose a reason for hiding this comment

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

According to NIST guidelines, passwords have to be long, not contain funny characters. I think we should have a default password setting of maybe 12 characters and allow customers to set their own REGEX. This will also allow us to keep short passwords in dev mode so we don't have to type funny stuff to login local server when developing 😎

We better read about current best practices though.

Copy link
Contributor

Choose a reason for hiding this comment

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

So currently NIST seems to care about password length and expiration is not recommended. I think standard was 14 chars at the moment without character enforcement. Maybe we should have a setting to allow customers setting minimal password length. idk if we should just keep what we had before and have standard 14 chars, and when strong password chosen, then go for 16 chars. Adding a new field for a custom length is probably an overkill. Lets discuss further.

Copy link
Contributor

@akostadinov akostadinov Jan 9, 2026

Choose a reason for hiding this comment

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

We should follow NIST SP 800-63B-4 Date Published: July 2025
https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63B-4.pdf

See section 3.1.1.2. Password Verifiers

Maybe we don't even need the strong password setting.

self.password = unencrypted_password
ThreeScale::Analytics.user_tracking(self).track('Migrated to BCrypt')
update_columns(password_digest: password_digest, salt: nil, crypted_password: nil)
authenticate(unencrypted_password)
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel uneasy about this whole file. What is it doing? Why is it wrapping? What was super?


def password_required?
signup.by_user? && super
(signup.by_user? || using_password?) && super
Copy link
Contributor

Choose a reason for hiding this comment

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

can you explain why do we need this change?

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we rename this method? What is super? I wonder if we should separate usage of password on signup and for validation.

Copy link
Contributor

@akostadinov akostadinov Jan 9, 2026

Choose a reason for hiding this comment

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

I asked AI whether the method can be split for checking whether password fields should be shown and whether password field has to be validated. Idk if the analysis makes sense but here it is:

● You're absolutely right - this is a confusing design! The same password_required? method is being used for two different purposes:

  1. Model validation - "Should we validate password presence when saving?"
  2. UI rendering - "Should we show password fields in forms?"

  And worse, it's got that triple-level super chain that's hard to follow.

  Better Design

  This could be much clearer with separate methods:

  For Validation:

  # In User model
  def validate_password_presence?
    signup.by_user? && password_digest_requires_validation? && legacy_password_requires_validation?
  end

  validates_presence_of :password, if: :validate_password_presence?

  For UI:

  # In User model  
  def needs_password_field?
    signup.by_user?  # Or even more specific: !using_external_auth?
  end

  # In templates
  {% if user.needs_password_field? %}
    <input name="account[user][password]" type="password">
  {% endif %}

  Why the current design exists

  Looking at the code, this seems to be a result of:
  1. Legacy migration - Moving from crypted_password to password_digest (bcrypt)
  2. Monkey-patching Rails - Overriding has_secure_password's password_required?
  3. No refactoring - Just kept adding super chains instead of cleaning it up

  The method does too many things and the name doesn't clearly indicate which concern it addresses.

public_search account_plans_ui_visible change_account_plan_permission service_plans_ui_visible
change_service_plan_permission enforce_sso
useraccountarea_enabled hide_service signups_enabled account_approval_required public_search
account_plans_ui_visible change_account_plan_permission service_plans_ui_visiblechange_service_plan_permission
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
account_plans_ui_visible change_account_plan_permission service_plans_ui_visiblechange_service_plan_permission
account_plans_ui_visible change_account_plan_permission service_plans_ui_visible change_service_plan_permission

@mayorova
Copy link
Contributor

To be honest, I find it quite annoying to not being able to set a "weak" password, especially it's uncomfortable in the development environment, where I'm not interested in security.
Also, customers can also have their test accounts, and it could be annoying to be obliged to use a 16-characters password... 😒

So, ideally, I'd prefer to keep the optional "strong password" setting. Whoever cares about security can enable it 😬

As for the admin portal,

About admin portal, there's no option to enable strong passwords, weak passwords are always accepted.

I think you can enable strong password on the Master portal, and this setting will be applied to the admin portal passwords. Unless this doesn't work as expected:
image

@jlledom jlledom force-pushed the THREESCALE-8916-admin-strong-paswords branch from 33fe198 to 25f4a95 Compare January 13, 2026 09:43
@jlledom jlledom self-assigned this Jan 14, 2026
@jlledom jlledom force-pushed the THREESCALE-8916-admin-strong-paswords branch 3 times, most recently from 101f7d4 to 7c97a77 Compare February 5, 2026 17:09
- Validate strong passwords in React forms
- Some forms treat the password field as clear text.
- Make password fields required only when they are actually required.
- `Current password` field visible and required only when it should.
- Password can be set for all users, no matter how did they signup.
- SSO Users: don't see the toast to change password.
  It can be changed from the same form
  This fixes https://issues.redhat.com/browse/THREESCALE-11548
- When enforce SSO is enabled, passwords can still be set or changed.
@jlledom jlledom force-pushed the THREESCALE-8916-admin-strong-paswords branch 2 times, most recently from f864657 to 891e8cb Compare February 6, 2026 12:39
jlledom and others added 4 commits February 6, 2026 13:51
This is to exclude John Doe from strong passwords validation

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@jlledom jlledom force-pushed the THREESCALE-8916-admin-strong-paswords branch from 891e8cb to 35acc2b Compare February 6, 2026 13:11
authorize! :update, user

user.update_with_flattened_attributes(flat_params)
user.update_with_flattened_attributes(flat_params, as: current_user.try(:role))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

PUT /admin/api/users/{id}.xml is adding this to it's call. This endpoint is the equivalent but for buyer users: PUT /admin/api/accounts/{id}/users/{id}.xml.

This is to unify behavior.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure this is relevant here. From what I can see this as: is used to prevent member permissions from being updated by member users - it's only allowed to admin users:

porta/app/models/user.rb

Lines 112 to 116 in 986e1a3

attr_accessible :title, :username, :email, :first_name, :last_name,
:conditions, :cas_identifier, :open_id, :service_conditions,
:job_role, :extra_fields, as: %i[default member admin]
attr_accessible :member_permission_service_ids, :member_permission_ids, as: %i[admin]

However, these member permissions thing is only applicable to admin users, not to buyer users, so I don't think adding as: changes anything.

It is true that on the code level there is no distinction between buyer users and admin users...

But I wouldn't add this 🤷

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But I wouldn't add this 🤷

By "this" you mean the as: in buyers_users_controller.rb:40? or the as: in user.rb:116?

Do you want me to remove this change?

Copy link
Contributor

Choose a reason for hiding this comment

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

I meant as: current_user.try(:role) in this controller. Unless this breaks something, of course 🤔

Well, I don't know if we need to remove it, I just find it a bit confusing to have it here, as it is not supposed to have any function for this controller, from what I understand.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done: 59aa2f7

{
signup_type: partner.signup_type,
password: permitted_params[:password].presence || SecureRandom.hex,
password: permitted_params[:password].presence,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The user is valid without a password when it's an SSO user. So there's no need to enforce a random password. Also, this random password is not shown to the caller anywhere, so it couldn't be used anyway.

After this change, The SSO users for partners don't have a password, the same as any other SSO user in the project.

@user = @account.users.build
@user.email = params[:email]
@user.password = SecureRandom.hex
@user.password = params[:password].presence
Copy link
Contributor Author

@jlledom jlledom Feb 6, 2026

Choose a reason for hiding this comment

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

Same, no password required.

Comment on lines 3 to 9
// IMPORTANT: These STRONG_PASSWORD_* constants are duplicated from the backend.
// The source of truth is app/lib/authentication/by_password.rb. If those constants change in Ruby,
// you must update them here as well. Do not modify these without updating the backend first.
const STRONG_PASSWORD_MIN_SIZE = 16
const RE_STRONG_PASSWORD = new RegExp(`^[ -~]{${STRONG_PASSWORD_MIN_SIZE},64}$`)
const STRONG_PASSWORD_FAIL_MSG = `Password must be at least ${STRONG_PASSWORD_MIN_SIZE} characters long, and contain only valid characters.`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Enforce the same NIST policy in frontend that we enforce in backend

validates :lost_password_token, :password_digest, length: { maximum: 255 }

attr_accessible :password, :password_confirmation
attr_accessible :password, :password_confirmation, as: %i[default member admin]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is to fix a mass-assignment error from PUT /admin/api/users/{id}.xml. Since the endpoint calls the update with role: :admin. The password was excluded from mass-assignment, because it only allowed the default role.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I understand... if there is no as: argument here, doesn't it mean that the role set in :as when assigning attributes doesn't matter?

Comment on lines 39 to 48
def validate_password?
password_digest_changed? || (signup.by_user? && password_digest.blank?)
end

def validate_strong_password?
return false if Rails.configuration.three_scale.strong_passwords_disabled
return false if signup.sample_data?

validate_password?
end
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This replaces password_required? Because it was pretty confusing:

  • Being called from views to decide whether making password inputs required. IMO that's wrong, password inputs are required or not according to their purpose, not to some computed value.
  • Being called also to decide whether validate the password or not. Which was wrong as well, since it didn't match all scenarios.

The new methods are tested and return proper values for all known scenarios.

email_part = email.split('@')
user_attributes = { email: "#{email_part[0]}+test@#{email_part[1]}", username: 'john', first_name: 'John',
last_name: 'Doe', password: '123456', password_confirmation: '123456', signup_type: :minimal}
last_name: 'Doe', password: '123456', password_confirmation: '123456', signup_type: :sample_data}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In order to exclude "John Doe" from strong password requirement, I added a new signup type :sample_data to identify it.

Copy link
Contributor

Choose a reason for hiding this comment

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

The question is, shouldn't we prevent sample weak passwords in production environment? I think we discussed this somewhere, about how to notify provider of the sample buyer user account. e.g. email/internal messaging.

But I'm not sure we came to an agreement.

// activerecord.errors.models.user.attributes.password.weak
const STRONG_PASSWORD_MIN_SIZE = 15
const RE_STRONG_PASSWORD = new RegExp(`^[ -~]{${STRONG_PASSWORD_MIN_SIZE},}$`)
const STRONG_PASSWORD_FAIL_MSG = `Password must be at least ${STRONG_PASSWORD_MIN_SIZE} characters long, and contain only valid characters.`
Copy link
Contributor

Choose a reason for hiding this comment

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

should we just check the size and not a RE?

Copy link
Contributor Author

@jlledom jlledom Feb 16, 2026

Choose a reason for hiding this comment

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

I'm checking this and I think we must in fact validate only the size. I took this RE from:

3. Verifiers and CSPs SHOULD accept all printing ASCII [RFC20] characters and the
space character in passwords.

But there's in fact a point #4 in the doc:

4. Verifiers and CSPs SHOULD accept Unicode [ISO/ISC 10646] characters in
passwords. Each Unicode code point SHALL be counted as a single character when
evaluating password length.

So the RE was wrong. I explain this in more detail on #4195 (comment).

@jlledom jlledom force-pushed the THREESCALE-8916-admin-strong-paswords branch from d35585c to 0d23aac Compare February 16, 2026 16:51
has_secure_password validations: false

validates_presence_of :password, if: :password_required?
before_validation :normalize_password, if: :validate_password?
Copy link
Contributor Author

@jlledom jlledom Feb 16, 2026

Choose a reason for hiding this comment

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

We must accept unicode characters in passwords. And ensure they are the same size no matter how they are represented.

We have a problem here: there are different ways to represent the same string in unicode:

https://www.honeybadger.io/blog/ruby-unicode-normalization/

W3c recommends composed (NFC): https://www.w3.org/International/questions/qa-html-css-normalization.en

Linux and windows send unicodes in that format, but MAC uses decomposed (NFD). In practice, that means a user could signup and set a password in MAC, and then won't be able to login from Linux.

In Rails 7.1 we have the normalizes method which makes the things much easier, but that doesn't work on virtual attributes like password and password_confirmation. For that, we'll have to wait till Rails 8.1:

https://blog.saeloun.com/2025/03/10/rails-introduces-active-model-normalization/

https://github.com/rails/rails/blob/v8.1.0/activemodel/CHANGELOG.md

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As a collateral effect of this change, those users using MAC and having non-ASCII characters in their passwords will have to reset it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm just wondering, is this only Safari or any browser running on mac? Does it affect iPhones or only macs?
Either way, I don't think we can help them but good to know for the release notes.

Copy link
Contributor

Choose a reason for hiding this comment

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

btw, did we allow multi-byte UTF chars previously?

Copy link
Contributor

Choose a reason for hiding this comment

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

According to your article, they recommend using NFKC for usernames, but maybe for passwords it is irrelevant as long as it is consistent 🤔

Copy link
Contributor Author

@jlledom jlledom Feb 17, 2026

Choose a reason for hiding this comment

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

Wrong link, I edited it, the correct one is from W3C: https://www.w3.org/International/questions/qa-html-css-normalization.en

The best way to ensure that these match is to use one particular Unicode
normalization form for all syntactic constructs, and as the default for
all authored content. It doesn't really matter whether you use the NFC or
NFD normalization form, it's more important that you are consistent. NFC
is, however, a good recommendation because this is what many (but not all)
keyboards tend to produce. (Most keyboards for European languages output
text in NFC already, but this is less likely to be the case if dealing with
many non-European languages.)

We don't recommend using any of the K forms in this way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

btw, did we allow multi-byte UTF chars previously?

I think we did, and as long as the user logged in using the same device they used when creating the password, that would cause no issues for them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm just wondering, is this only Safari or any browser running on mac? Does it affect iPhones or only macs? Either way, I don't think we can help them but good to know for the release notes.

I took Claude's word for this, now I verified it and in fact it was a hallucination.

Browsers send whatever the user types in the input. Apparently this is normally NFC for most cases, but there could be corner cases like writing from a in-screen keyboard, or copy-pasting from a document in NFD, that would lead in the browser sending NFD.

Comment on lines +112 to +115
def authenticate(password)
authenticate_without_normalization(password&.unicode_normalize(:nfc))
end
alias_method :authenticated?, :authenticate
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is needed so authenticate? also normalizes before comparing the pass to DB

assert_select 'input[name="user[password_confirmation]"]'
end

test 'edit page shows password fields for SSO user without password' do
Copy link
Contributor

Choose a reason for hiding this comment

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

wasn't there some scenario where we should NOT show the fields?

@provider_account.payment_gateway_options[:merchant_id] = "my-payment-gw-mid"
@provider_account.payment_gateway_options[:public_key] = "AnY-pUbLiC-kEy"
@provider_account.payment_gateway_options[:private_key] = "a1b2c3d4e5"
login_with @buyer_account.admins.first.username, 'supersecret'
Copy link
Contributor

Choose a reason for hiding this comment

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

was this login line intentionally removed?

}
def password_required?
@user.password_required?
@user.signup.by_user?
Copy link
Contributor

Choose a reason for hiding this comment

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

why was this change?

And I fill in "Current password" with "superSecret1234#"
And I press "Update Details"
Then field "New password" has inline error "is too short (minimum is 6 characters)"
Then I should see the error that the password is too weak No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

no new line char at end ?!?!?!??!?

force_ssl: false
report_traffic: false
secure_cookie: false
strong_passwords_disabled: true
Copy link
Contributor

Choose a reason for hiding this comment

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

disabled by default?


def using_password?
password_digest.present?
password_digest_in_database.present?
Copy link
Contributor

Choose a reason for hiding this comment

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

wait, if password_digest was set but not yet in the database, shouldn't we still return true here?

self.lost_password_token_generated_at = Time.current

token
token if save(validate: false)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we still need validate: false now that we validate on password change only?


def validate_strong_password?
return false if Rails.configuration.three_scale.strong_passwords_disabled
return false if signup.sample_data?
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return false if signup.sample_data?
return false if signup.sample_data? && !Rails.env.production?

Follow up on previous question about sample data ending up in production, I would imagine the logic to something like this.

self.lost_password_token = nil
end

# To avoid all this logic, from Rails 8.1+ we can use
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# To avoid all this logic, from Rails 8.1+ we can use
raise "FIXME" if Gem::Version.new(Rails.version) >= Gem::Version.new("8.1")
# To avoid all this logic, from Rails 8.1+ we can use

\z
/x
STRONG_PASSWORD_FAIL_MSG = "Password must be at least 8 characters long, and contain both upper and lowercase letters, a digit and one special character of #{SPECIAL_CHARACTERS}.".freeze
STRONG_PASSWORD_MIN_SIZE = 15
Copy link
Contributor

Choose a reason for hiding this comment

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

it might be a good idea in case some customers want to apply higher level of security, as you wish.

Suggested change
STRONG_PASSWORD_MIN_SIZE = 15
STRONG_PASSWORD_MIN_SIZE = ENV["THREESCALE_PASSWORD_MIN_SIZE", 15]

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.

3 participants