diff --git a/Gemfile b/Gemfile index 957e3f9..1c4d4ea 100755 --- a/Gemfile +++ b/Gemfile @@ -18,4 +18,7 @@ group :test do end # Add these gems for WebAuthn support -gem "webauthn", "~> 3.1" +gem "webauthn" + +# Add these gems for pwened password support +gem "pwned" diff --git a/Gemfile.lock b/Gemfile.lock index de04b42..bab26c6 100755 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -90,9 +90,9 @@ GEM cbor (0.5.9.8) childprocess (5.1.0) logger (~> 1.5) - concurrent-ruby (1.3.3) + concurrent-ruby (1.3.4) connection_pool (2.4.1) - cose (1.3.0) + cose (1.3.1) cbor (~> 0.5.9) openssl-signature_algorithm (~> 1.0) crass (1.0.6) @@ -152,6 +152,7 @@ GEM public_suffix (6.0.1) puma (6.4.2) nio4r (~> 2.0) + pwned (2.4.1) racc (1.8.1) rack (3.1.7) rack-session (2.0.0) @@ -249,10 +250,11 @@ DEPENDENCIES letter_opener minitest-stub_any_instance puma + pwned simplecov sprockets-rails sqlite3 (~> 1.7) - webauthn (~> 3.1) + webauthn BUNDLED WITH 2.4.22 diff --git a/README.md b/README.md index 5126442..db0bbc1 100755 --- a/README.md +++ b/README.md @@ -15,12 +15,13 @@ user experience akin to that offered by the well-regarded Devise gem. - [Routes](#routes) - [Helper Methods](#helper-methods) - [Restricting and Changing Routes](#restricting-and-changing-routes) -5. [WebAuthn](#webauthn) -6. [Within Your Application](#within-your-application) -7. Customizing +5. [Have I Been Pwned](#have-i-been-pwned) +6. [WebAuthn](#webauthn) +7. [Within Your Application](#within-your-application) +8. Customizing - [Sign In Page](https://github.com/kobaltz/action_auth/wiki/Overriding-Sign-In-page-view) -7. [License](#license) -8. [Credits](#credits) +9. [License](#license) +10. [Credits](#credits) ## Breaking Changes @@ -130,6 +131,8 @@ These are the planned features for ActionAuth. The ones that are checked off are ⏳ - OAuth with Google, Facebook, Github, Twitter, etc. +✅ - Have I Been Pwned Integration + ✅ - Account Deletion ⏳ - Account Lockout @@ -206,13 +209,15 @@ versus a user that is not logged in. end root to: 'welcome#index' -## WebAuthn +## Have I Been Pwned -ActionAuth's approach for WebAuthn is simplicity. It is used as a multifactor authentication step, -so users will still need to register their email address and password. Once the user is registered, -they can add a Passkey to their account. The Passkey could be an iCloud Keychain, a hardware security -key like a Yubikey, or a mobile device. If enabled and configured, the user will be prompted to use -their Passkey after they log in. +[Have I Been Pwned](https://haveibeenpwned.com/) is a way that youre able to check if a password has been compromised in a data breach. This is a great way to ensure that your users are using secure passwords. + +Add the `pwned` gem to your Gemfile. That's all you'll have to do to enable this functionality. + +```ruby +bundle add pwned +``` ## Magic Links @@ -236,6 +241,13 @@ will want to style this to fit your application and have some kind of confirmati <%= button_to "Delete Account", action_auth.users_path, method: :delete %>
``` +## WebAuthn + +ActionAuth's approach for WebAuthn is simplicity. It is used as a multifactor authentication step, +so users will still need to register their email address and password. Once the user is registered, +they can add a Passkey to their account. The Passkey could be an iCloud Keychain, a hardware security +key like a Yubikey, or a mobile device. If enabled and configured, the user will be prompted to use +their Passkey after they log in. #### Configuration diff --git a/app/controllers/action_auth/identity/password_resets_controller.rb b/app/controllers/action_auth/identity/password_resets_controller.rb index e5aef91..a138ea0 100644 --- a/app/controllers/action_auth/identity/password_resets_controller.rb +++ b/app/controllers/action_auth/identity/password_resets_controller.rb @@ -2,6 +2,7 @@ module ActionAuth module Identity class PasswordResetsController < ApplicationController before_action :set_user, only: %i[ edit update ] + before_action :validate_pwned_password, only: :update def new end @@ -41,6 +42,16 @@ def user_params def send_password_reset_email UserMailer.with(user: @user).password_reset.deliver_later end + + def validate_pwned_password + return unless ActionAuth.configuration.pwned_enabled? + + pwned = Pwned::Password.new(params[:password]) + if pwned.pwned? + @user.errors.add(:password, "has been pwned #{pwned.pwned_count} times. Please choose a different password.") + render :edit, status: :unprocessable_entity + end + end end end end diff --git a/app/controllers/action_auth/passwords_controller.rb b/app/controllers/action_auth/passwords_controller.rb index cdbfb02..7258738 100755 --- a/app/controllers/action_auth/passwords_controller.rb +++ b/app/controllers/action_auth/passwords_controller.rb @@ -1,6 +1,7 @@ module ActionAuth class PasswordsController < ApplicationController before_action :set_user + before_action :validate_pwned_password, only: :update def edit end @@ -22,5 +23,15 @@ def set_user def user_params params.permit(:password, :password_confirmation, :password_challenge).with_defaults(password_challenge: "") end + + def validate_pwned_password + return unless ActionAuth.configuration.pwned_enabled? + + pwned = Pwned::Password.new(params[:password]) + if pwned.pwned? + @user.errors.add(:password, "has been pwned #{pwned.pwned_count} times. Please choose a different password.") + render :new, status: :unprocessable_entity + end + end end end diff --git a/app/controllers/action_auth/registrations_controller.rb b/app/controllers/action_auth/registrations_controller.rb index 17a0a07..40d64d1 100755 --- a/app/controllers/action_auth/registrations_controller.rb +++ b/app/controllers/action_auth/registrations_controller.rb @@ -1,5 +1,7 @@ module ActionAuth class RegistrationsController < ApplicationController + before_action :validate_pwned_password, only: :create + def new @user = User.new end @@ -23,12 +25,25 @@ def create end private - def user_params - params.permit(:email, :password, :password_confirmation) - end - def send_email_verification - UserMailer.with(user: @user).email_verification.deliver_later + def user_params + params.permit(:email, :password, :password_confirmation) + end + + def send_email_verification + UserMailer.with(user: @user).email_verification.deliver_later + end + + def validate_pwned_password + return unless ActionAuth.configuration.pwned_enabled? + + pwned = Pwned::Password.new(params[:password]) + + if pwned.pwned? + @user = User.new(email: params[:email]) + @user.errors.add(:password, "has been pwned #{pwned.pwned_count} times. Please choose a different password.") + render :new, status: :unprocessable_entity end + end end end diff --git a/app/views/action_auth/identity/password_resets/edit.html.erb b/app/views/action_auth/identity/password_resets/edit.html.erb index ae594f9..18d3430 100644 --- a/app/views/action_auth/identity/password_resets/edit.html.erb +++ b/app/views/action_auth/identity/password_resets/edit.html.erb @@ -15,18 +15,18 @@ <%= form.hidden_field :sid, value: params[:sid] %> -