minimalist_authentication 3.0.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -29
  3. data/app/controllers/email_verifications_controller.rb +5 -4
  4. data/app/controllers/emails_controller.rb +1 -1
  5. data/app/controllers/password_resets_controller.rb +21 -12
  6. data/app/controllers/passwords_controller.rb +13 -8
  7. data/app/helpers/minimalist_authentication/application_helper.rb +68 -0
  8. data/app/mailers/minimalist_authentication_mailer.rb +9 -13
  9. data/app/views/email_verifications/show.html.erb +1 -1
  10. data/app/views/emails/edit.html.erb +3 -3
  11. data/app/views/minimalist_authentication_mailer/update_password.html.erb +3 -1
  12. data/app/views/minimalist_authentication_mailer/update_password.text.erb +2 -2
  13. data/app/views/minimalist_authentication_mailer/verify_email.html.erb +9 -3
  14. data/app/views/minimalist_authentication_mailer/verify_email.text.erb +3 -3
  15. data/app/views/password_resets/new.html.erb +5 -6
  16. data/app/views/passwords/edit.html.erb +9 -7
  17. data/app/views/sessions/_form.html.erb +7 -7
  18. data/app/views/sessions/new.html.erb +3 -3
  19. data/config/locales/minimalist_authentication.en.yml +31 -9
  20. data/config/routes.rb +3 -7
  21. data/lib/minimalist_authentication/authenticator.rb +22 -15
  22. data/lib/minimalist_authentication/configuration.rb +10 -13
  23. data/lib/minimalist_authentication/controller.rb +3 -3
  24. data/lib/minimalist_authentication/email_verification.rb +29 -11
  25. data/lib/minimalist_authentication/engine.rb +5 -0
  26. data/lib/minimalist_authentication/sessions.rb +1 -1
  27. data/lib/minimalist_authentication/user.rb +54 -19
  28. data/lib/minimalist_authentication/verifiable_token.rb +21 -39
  29. data/lib/minimalist_authentication/version.rb +1 -1
  30. data/lib/minimalist_authentication.rb +10 -0
  31. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a82f3a3833f400b37555173885d052000dfbd5e4a67da90068384558f8d97705
4
- data.tar.gz: 48b819ddbb26a5cb5deaa03c5cd3ef943394e93d6390286d3f30cbae7e5e8477
3
+ metadata.gz: ba64bc8499dedd0f4fe8dfca813cc0c21e2d32759bd3b3b4dd8aac3c0e77605a
4
+ data.tar.gz: 566e4b373c274fd7843469d4d225560baf877aabde97658b1aa5a66f3c97f65e
5
5
  SHA512:
6
- metadata.gz: c867bd7daebdce0f0998e78db4d05ed89bd8d3767d6bb0dc79e362f74ca8e2ac43e930dffb0f396d3d1f4f6b517d26fee00e03e06c32a8afd4b52fb0d03984b7
7
- data.tar.gz: de8329fcb5b6bd2d01e9a421818952e6f362d4ff16a3e060d30e5b3afbd4751d56e4c0d37b1a3d80d917baf48dea36b407a2fbfc8f647019fa896529d82bf3a2
6
+ metadata.gz: 149c5f1eb8a13319ba9d4848a7851107433d96081ed9a1bf01e43cba0366cb1e873aaa47c10eefed29aba9b973bf53076ecefd6d7b6835bb1a9300a4db8813fc
7
+ data.tar.gz: 51aae04db4c7bcdf4a26009fc1278aa716aea12d95624fe74ffd21fb9d52e0c901ec78d15751df92c2ce2882544a54b80f623e72a53703e1a67b88072662aed9
data/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # MinimalistAuthentication
2
- A Rails authentication gem that takes a minimalist approach. It is designed to be simple to understand, use, and modify for your application.
2
+ A Rails authentication gem that takes a minimalist approach. It is designed to be simple to understand, use, and customize for your application.
3
3
 
4
4
 
5
5
  ## Installation
6
6
  Add this line to your application's Gemfile:
7
7
 
8
8
  ```ruby
9
- gem 'minimalist_authentication'
9
+ gem "minimalist_authentication"
10
10
  ```
11
11
 
12
- And then execute:
12
+ And then run:
13
13
  ```bash
14
14
  $ bundle
15
15
  ```
@@ -61,19 +61,29 @@ class ActiveSupport::TestCase
61
61
  end
62
62
  ```
63
63
 
64
+
64
65
  ## Configuration
65
66
  Customize the configuration with an initializer. Create a **minimalist_authentication.rb** file in config/initializers.
66
67
  ```ruby
67
68
  MinimalistAuthentication.configure do |configuration|
68
- configuration.user_model_name = 'CustomModelName' # default is '::User'
69
+ configuration.login_redirect_path = :custom_path # default is :root_path
70
+ configuration.logout_redirect_path = :custom_path # default is :new_session_path
71
+ configuration.request_email = true # default is true
69
72
  configuration.session_key = :custom_session_key # default is :user_id
73
+ configuration.user_model_name = "CustomModelName" # default is "::User"
70
74
  configuration.validate_email = true # default is true
71
75
  configuration.validate_email_presence = true # default is true
72
- configuration.request_email = true # default is true
73
76
  configuration.verify_email = true # default is true
74
- configuration.login_redirect_path = :custom_path # default is :root_path
75
- configuration.logout_redirect_path = :custom_path # default is :new_session_path
76
- configuration.email_prefix = '[Custom Prefix]' # default is application name
77
+ end
78
+ ```
79
+
80
+ ### Example with a Person Model
81
+ ```ruby
82
+ MinimalistAuthentication.configure do |configuration|
83
+ configuration.login_redirect_path = :dashboard_path
84
+ configuration.session_key = :person_id
85
+ configuration.user_model_name = "Person"
86
+ configuration.validate_email_presence = false
77
87
  end
78
88
  ```
79
89
 
@@ -87,38 +97,44 @@ example_user:
87
97
  ```
88
98
 
89
99
 
90
- ## Verification Tokens
91
- Verification token support is provided by the **MinimalistAuthentication::VerifiableToken**
92
- module. Include the module in your user class and add the verification token columns
93
- to the database.
94
-
95
- Include MinimalistAuthentication::VerifiableToken in your user model (app/models/user.rb)
100
+ ## Email Verification
101
+ Include MinimalistAuthentication::EmailVerification in your user model (app/models/user.rb)
96
102
  ```ruby
97
103
  class User < ApplicationRecord
98
104
  include MinimalistAuthentication::User
99
- include MinimalistAuthentication::VerifiableToken
105
+ include MinimalistAuthentication::EmailVerification
100
106
  end
101
107
  ```
102
108
 
103
- Add the **verification_token** and **verification_token_generated_at** columns:
104
- Create a user model with **email** for an identifier:
109
+ Add the **email_verified_at** column to your user model:
105
110
  ```bash
106
- bin/rails generate migration AddVerificationTokenToUsers verification_token:string:uniq verification_token_generated_at:datetime
111
+ bin/rails generate migration AddEmailVerifiedAtToUsers email_verified_at:datetime
107
112
  ```
108
113
 
109
- ### Email Verification
110
- Include MinimalistAuthentication::EmailVerification in your user model (app/models/user.rb)
114
+
115
+ ## Verification Tokens
116
+ Verification token support is provided by the ```ActiveRecord::TokenFor#generate_token_for``` method. MinimalistAuthentication includes token definitions for **password_reset** and **email_verification**. These tokens are utilized by the **update_password** and **verify_email** email messages respectively, to allow users to update their passwords and verify their email addresses.
117
+
118
+ ### Update Password
119
+ The **update_password** token expires in 1 hour and is invalidated when the user's password is changed.
120
+
121
+ #### Example
111
122
  ```ruby
112
- class User < ApplicationRecord
113
- include MinimalistAuthentication::User
114
- include MinimalistAuthentication::VerifiableToken
115
- include MinimalistAuthentication::EmailVerification
116
- end
123
+ token = user.generate_token_for(:password_reset)
124
+ User.find_by_token_for(:password_reset, token) # => user
125
+ user.update!(password: "new password")
126
+ User.find_by_token_for(:password_reset, token) # => nil
117
127
  ```
118
128
 
119
- Add the **email_verified_at** column to your user model:
120
- ```bash
121
- bin/rails generate migration AddEmailVerifiedAtToUsers email_verified_at:datetime
129
+ ### Email Verification
130
+ The **email_verification** token expires in 1 hour and is invalidated when the user's email is changed.
131
+
132
+ #### Example
133
+ ```ruby
134
+ token = user.generate_token_for(:email_verification)
135
+ User.find_by_token_for(:email_verification, token) # => user
136
+ user.update!(email: "new_email@example.com")
137
+ User.find_by_token_for(:email_verification, token) # => nil
122
138
  ```
123
139
 
124
140
 
@@ -127,7 +143,7 @@ bin/rails generate migration AddEmailVerifiedAtToUsers email_verified_at:datetim
127
143
  ### Upgrading to Version 2.0
128
144
  Pre 2.0 versions of MinimalistAuthentication supported multiple hash algorithms
129
145
  and stored the hashed password and salt as separate fields in the database
130
- (crypted_password and salt). The current version of MinimalistAuthentication
146
+ (crypted_password and salt). The 2.0 version of MinimalistAuthentication
131
147
  uses BCrypt to hash passwords and stores the result in the **password_hash** field.
132
148
 
133
149
  To convert from a pre 2.0 version add the **password_hash** to your user model
@@ -163,5 +179,9 @@ end
163
179
  alias_attribute :password_digest, :password_hash
164
180
  ```
165
181
 
182
+ ### Upgrading to Version 3.2
183
+ The **verification_token** and **verification_token_generated_at** database columns are no longer used and can be safely removed from your user model.
184
+
185
+
166
186
  ## License
167
187
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT)..
@@ -1,18 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class EmailVerificationsController < ApplicationController
4
+ # Verifies the email of the current_user using the provided token
4
5
  def show
5
6
  current_user.verify_email(params[:token])
6
7
  end
7
8
 
9
+ # Form for current_user to request an email verification email
8
10
  def new
9
- # verify email for current_user
11
+ # new.html.erb
10
12
  end
11
13
 
14
+ # Sends an email verification email to the current_user
12
15
  def create
13
- current_user.regenerate_verification_token
14
- MinimalistAuthenticationMailer.verify_email(current_user).deliver_now
15
-
16
+ MinimalistAuthenticationMailer.with(user: current_user).verify_email.deliver_now
16
17
  redirect_to dashboard_path, notice: t(".notice", email: current_user.email)
17
18
  end
18
19
  end
@@ -7,7 +7,7 @@ class EmailsController < ApplicationController
7
7
  if current_user.update(user_params)
8
8
  redirect_to update_redirect_path, notice: t(".notice")
9
9
  else
10
- render :edit
10
+ render :edit, status: :unprocessable_entity
11
11
  end
12
12
  end
13
13
 
@@ -5,30 +5,39 @@ class PasswordResetsController < ApplicationController
5
5
 
6
6
  layout "sessions"
7
7
 
8
- # Form for user to request a password reset
8
+ # Renders form for user to request a password reset
9
9
  def new
10
- @user = MinimalistAuthentication.configuration.user_model.new
10
+ # new.html.erb
11
11
  end
12
12
 
13
13
  # Send a password update link to users with a verified email
14
14
  def create
15
- if user
16
- user.regenerate_verification_token
17
- MinimalistAuthenticationMailer.update_password(user).deliver_now
15
+ if email_valid?
16
+ send_update_password_email if user
17
+
18
+ # Always display notice to prevent leaking user emails
19
+ redirect_to new_session_path, notice: t(".notice", email:)
20
+ else
21
+ flash.now.alert = t(".alert")
22
+ render :new, status: :unprocessable_entity
18
23
  end
19
- # always display notice even if the user was not found to prevent leaking user emails
20
- redirect_to new_session_path, notice: "Password reset instructions were mailed to #{email}"
21
24
  end
22
25
 
23
26
  private
24
27
 
25
- def user
26
- return unless URI::MailTo::EMAIL_REGEXP.match?(email)
28
+ def email
29
+ params.dig(:user, :email)
30
+ end
27
31
 
28
- @user ||= MinimalistAuthentication.configuration.user_model.active.email_verified.find_by(email:)
32
+ def send_update_password_email
33
+ MinimalistAuthenticationMailer.with(user:).update_password.deliver_now
29
34
  end
30
35
 
31
- def email
32
- params.dig(:user, :email)
36
+ def user
37
+ @user ||= MinimalistAuthentication.user_model.find_by_verified_email(email:)
38
+ end
39
+
40
+ def email_valid?
41
+ URI::MailTo::EMAIL_REGEXP.match?(email)
33
42
  end
34
43
  end
@@ -3,34 +3,39 @@
3
3
  class PasswordsController < ApplicationController
4
4
  skip_before_action :authorization_required
5
5
 
6
+ before_action :validate_token, only: %i[edit update]
7
+
6
8
  layout "sessions"
7
9
 
8
10
  # From for user to update password
9
11
  def edit
10
- redirect_to(new_session_path) unless user.matches_verification_token?(token)
11
- # render passwords/edit.html.erb
12
+ # edit.html.erb
12
13
  end
13
14
 
14
15
  # Update user's password
15
16
  def update
16
- if user.secure_update(token, password_params.merge(password_required: true))
17
+ if user.update(password_params.merge(password_required: true))
17
18
  redirect_to new_session_path, notice: t(".notice")
18
19
  else
19
- render :edit
20
+ render :edit, status: :unprocessable_entity
20
21
  end
21
22
  end
22
23
 
23
24
  private
24
25
 
25
- def user
26
- @user ||= MinimalistAuthentication.configuration.user_model.find(params[:user_id])
26
+ def password_params
27
+ params.require(:user).permit(:password, :password_confirmation)
27
28
  end
28
29
 
29
30
  def token
30
31
  @token ||= params[:token]
31
32
  end
32
33
 
33
- def password_params
34
- params.require(:user).permit(:password, :password_confirmation)
34
+ def user
35
+ @user ||= MinimalistAuthentication.user_model.active.find_by_token_for(:password_reset, token)
36
+ end
37
+
38
+ def validate_token
39
+ redirect_to(new_session_path, alert: t(".invalid_token")) unless user
35
40
  end
36
41
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MinimalistAuthentication
4
+ module ApplicationHelper
5
+ def ma_confirm_password_field(form, options = {})
6
+ form.password_field(
7
+ :password_confirmation,
8
+ options.reverse_merge(
9
+ autocomplete: "new-password",
10
+ minlength: MinimalistAuthentication.user_model.password_minimum,
11
+ placeholder: t(".password_confirmation.placeholder"),
12
+ required: true
13
+ )
14
+ )
15
+ end
16
+
17
+ def ma_email_field(form, options = {})
18
+ form.email_field(
19
+ :email,
20
+ options.reverse_merge(
21
+ autocomplete: "email",
22
+ autofocus: true,
23
+ placeholder: t(".email.placeholder", default: true),
24
+ required: true
25
+ )
26
+ )
27
+ end
28
+
29
+ def ma_forgot_password_link
30
+ link_to(t(".forgot_password"), new_password_reset_path)
31
+ end
32
+
33
+ def ma_new_password_field(form, options = {})
34
+ form.password_field(
35
+ :password,
36
+ options.reverse_merge(
37
+ autocomplete: "new-password",
38
+ minlength: MinimalistAuthentication.user_model.password_minimum,
39
+ placeholder: t(".password.placeholder"),
40
+ required: true
41
+ )
42
+ )
43
+ end
44
+
45
+ def ma_password_field(form, options = {})
46
+ form.password_field(
47
+ :password,
48
+ options.reverse_merge(
49
+ autocomplete: "current-password",
50
+ placeholder: true,
51
+ required: true
52
+ )
53
+ )
54
+ end
55
+
56
+ def ma_username_field(form, options = {})
57
+ form.text_field(
58
+ :username,
59
+ options.reverse_merge(
60
+ autocomplete: "username",
61
+ autofocus: true,
62
+ placeholder: true,
63
+ required: true
64
+ )
65
+ )
66
+ end
67
+ end
68
+ end
@@ -1,24 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class MinimalistAuthenticationMailer < ApplicationMailer
4
- def verify_email(user)
5
- @verify_email_link = email_verification_url(token: user.verification_token)
6
- send_to(user, "Email Address Verification")
4
+ before_action { @user = params[:user] }
5
+ after_action :mail_user
6
+
7
+ def verify_email
8
+ @verify_email_url = email_verification_url(token: @user.generate_token_for(:email_verification))
7
9
  end
8
10
 
9
- def update_password(user)
10
- @edit_password_link = edit_user_password_url(user, token: user.verification_token)
11
- send_to(user, "Update Password")
11
+ def update_password
12
+ @edit_password_url = edit_password_url(token: @user.generate_token_for(:password_reset))
12
13
  end
13
14
 
14
15
  private
15
16
 
16
- def send_to(user, subject)
17
- @user = user
18
- mail to: @user.email, subject: prefixed_subject(subject)
19
- end
20
-
21
- def prefixed_subject(subject)
22
- "#{MinimalistAuthentication.configuration.email_prefix} #{subject}"
17
+ def mail_user
18
+ mail(to: @user.email)
23
19
  end
24
20
  end
@@ -7,4 +7,4 @@
7
7
  <% end %>
8
8
  <h2><%= current_user.email %></h2>
9
9
 
10
- <%= link_to('Go to Dashboard', dashboard_path) %>
10
+ <%= link_to("Go to Dashboard", dashboard_path) %>
@@ -2,11 +2,11 @@
2
2
 
3
3
  <h2>Please update your email address</h2>
4
4
 
5
- <%= form_for(current_user, as: :user, url: email_path) do |form| %>
5
+ <%= form_with(model: current_user, url: email_path) do |form| %>
6
6
  <%= form.text_field :email %>
7
- <%= form.submit 'Update' %>
7
+ <%= form.submit "Update" %>
8
8
  <% end %>
9
9
 
10
10
  <p>Providing your email will allow you to receive confidential messages and reset your password.</p>
11
11
 
12
- <%= link_to('Skip Email Update', dashboard_path) %>
12
+ <%= link_to("Skip Email Update", dashboard_path) %>
@@ -1,3 +1,5 @@
1
1
  <p><%= t('.opening') %></p>
2
2
 
3
- <%= link_to @edit_password_link, @edit_password_link %>
3
+ <p>
4
+ <%= link_to("Update Password", @edit_password_url, rel: "noopener noreferrer", target: "_blank") %>
5
+ </p>
@@ -1,3 +1,3 @@
1
- <%= t('.opening') %>
1
+ <%= t(".opening") %>
2
2
 
3
- <%= @edit_password_link %>
3
+ <%= @edit_password_url %>
@@ -1,5 +1,11 @@
1
- <p><%= t('.opening') %></p>
1
+ <p>
2
+ <%= t(".opening") %>
3
+ </p>
2
4
 
3
- <%= link_to @verify_email_link, @verify_email_link %>
5
+ <p>
6
+ <%= link_to("Verify Email Address", @verify_email_url, rel: "noopener noreferrer", target: "_blank") %>
7
+ </p>
4
8
 
5
- <p><%= t('.closing') %></p>
9
+ <p>
10
+ <%= t(".closing") %>
11
+ </p>
@@ -1,5 +1,5 @@
1
- <%= t('.opening') %>
1
+ <%= t(".opening") %>
2
2
 
3
- <%= @verify_email_link %>
3
+ <%= @verify_email_url %>
4
4
 
5
- <%= t('.closing') %>
5
+ <%= t(".closing") %>
@@ -1,13 +1,12 @@
1
- <h1>Request Password Reset</h1>
1
+ <h1><%= t(".title") %></h1>
2
2
 
3
3
  <p>
4
- Enter your email address we'll send you a link to reset your password.
4
+ <%= t(".instructions") %>
5
5
  </p>
6
6
 
7
- <%= form_for @user, as: :user, url: password_reset_path do |form| %>
7
+ <%= form_with scope: :user, url: password_reset_path do |form| %>
8
8
  <div>
9
- <%= form.label :email %>
10
- <%= form.text_field :email %>
9
+ <%= ma_email_field(form) %>
11
10
  </div>
12
- <%= form.submit 'Reset Password' %>
11
+ <%= form.submit t(".submit") %>
13
12
  <% end %>
@@ -1,15 +1,17 @@
1
- <h1>Edit Password</h1>
1
+ <h1><%= t(".title") %></h1>
2
+
3
+ <p><%= t(".instructions") %></p>
2
4
 
3
5
  <%= @user.errors.full_messages if @user.errors.any? %>
4
6
 
5
- <%= form_for @user, as: :user, url: user_password_path(@user, token: @token), html: { method: :put } do |form| %>
7
+ <%= form_with model: @user, url: password_path(token: @token) do |form| %>
6
8
  <div>
7
- <%= form.label :password %>
8
- <%= form.password_field :password %>
9
+ <%= form.label :password %>
10
+ <%= ma_new_password_field(form) %>
9
11
  </div>
10
12
  <div>
11
- <%= form.label :password_confirmation %>
12
- <%= form.password_field :password_confirmation %>
13
+ <%= form.label :password_confirmation %>
14
+ <%= ma_confirm_password_field(form) %>
13
15
  </div>
14
- <%= form.submit 'Update Password' %>
16
+ <%= form.submit t(".submit") %>
15
17
  <% end %>
@@ -1,11 +1,11 @@
1
- <%= form_for @user, url: session_path do |form| %>
1
+ <%= form_with model: @user, url: session_path do |form| %>
2
2
  <div>
3
- <%= form.label :email %>
4
- <%= form.text_field :email %>
3
+ <%= form.label :email %>
4
+ <%= ma_email_field(form) %>
5
5
  </div>
6
- <div>
7
- <%= form.label :password %>
8
- <%= form.password_field :password %>
6
+ <div style="margin-top: 8px;">
7
+ <%= form.label :password %>
8
+ <%= ma_password_field(form) %>
9
9
  </div>
10
- <%= form.submit 'Log in' %>
10
+ <%= form.submit t("sessions.new.submit") %>
11
11
  <% end %>
@@ -1,3 +1,3 @@
1
- <h1>Login</h1>
2
- <%= render partial: 'form' %>
3
- <%= link_to 'I forgot', new_password_reset_path %>
1
+ <h1><%= t(".title") %></h1>
2
+ <%= render partial: "form" %>
3
+ <%= ma_forgot_password_link %>
@@ -1,12 +1,38 @@
1
1
  en:
2
- # controllers
3
2
  email_verifications:
4
3
  create:
5
4
  notice: Verification email sent to %{email}, follow the instructions to complete verification. Thank you!
6
5
  emails:
7
6
  update:
8
7
  notice: Email successfully updated
8
+ minimalist_authentication_mailer:
9
+ update_password:
10
+ opening: Please click the link below to update your password.
11
+ subject: Update Password
12
+ verify_email:
13
+ closing: If you did not request email verification you can safely ignore this message.
14
+ opening: Please click the link below to complete your email verification.
15
+ subject: Email Address Verification
16
+ password_resets:
17
+ new:
18
+ email:
19
+ placeholder: Enter your email address
20
+ instructions: Enter your verified email address, and we'll send you a link to reset your password.
21
+ submit: Send Reset Link
22
+ title: Reset Your Password
23
+ create:
24
+ alert: Please provide a valid email address.
25
+ notice: We've sent password reset instructions to %{email}. If you don't receive the email within a few minutes, please check your spam folder.
9
26
  passwords:
27
+ edit:
28
+ instructions: Please enter your new password.
29
+ password:
30
+ placeholder: New password
31
+ password_confirmation:
32
+ placeholder: Confirm password
33
+ submit: Reset Password
34
+ title: Reset Password
35
+ invalid_token: Your password reset link has expired. Please request a new one to continue.
10
36
  update:
11
37
  notice: Password successfully updated
12
38
  sessions:
@@ -14,11 +40,7 @@ en:
14
40
  alert: Couldn't log you in as %{identifier}
15
41
  destroy:
16
42
  notice: You have been logged out.
17
-
18
- # mailers
19
- minimalist_authentication_mailer:
20
- update_password:
21
- opening: Please click the link below to update your password.
22
- verify_email:
23
- opening: Please click the link below to complete your email verification.
24
- closing: If you did not request email verification you can safely ignore this message.
43
+ new:
44
+ forgot_password: Forgot password?
45
+ title: Sign In
46
+ submit: Sign In
data/config/routes.rb CHANGED
@@ -1,12 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Rails.application.routes.draw do
4
- resources :user, only: [] do
5
- resource :password, only: %i[edit update]
6
- end
7
-
8
- resource :password_reset, only: %i[new create]
9
-
4
+ resource :email_verification, only: %i[show new create]
10
5
  resource :email, only: %i[edit update]
11
- resource :email_verification, only: %i[new create show]
6
+ resource :password_reset, only: %i[new create]
7
+ resource :password, only: %i[edit update]
12
8
  end
@@ -13,37 +13,44 @@ module MinimalistAuthentication
13
13
  # Params examples:
14
14
  # { email: 'user@example.com', password: 'abc123' }
15
15
  # { username: 'user', password: 'abc123' }
16
- # Returns user object upon successful authentication.
17
- def self.authenticated_user(params)
16
+ def self.authenticate(params)
18
17
  hash = params.to_h.with_indifferent_access
18
+ field, value = hash.find { |key, _| LOGIN_FIELDS.include?(key) }
19
+ new(field:, value:, password: hash["password"]).authenticated_user
20
+ end
19
21
 
20
- # Extract login field from hash
21
- field = (hash.keys & LOGIN_FIELDS).first
22
-
23
- # Attempt to authenticate user
24
- new(field:, value: hash[field], password: hash["password"]).authenticated_user
22
+ def self.authenticated_user(params)
23
+ MinimalistAuthentication.deprecator.warn(<<-MSG.squish)
24
+ Calling MinimalistAuthentication::Authenticator.authenticated_user is deprecated.
25
+ Use MinimalistAuthentication::Authenticator.authenticate instead.
26
+ MSG
27
+ authenticate(params)
25
28
  end
26
29
 
30
+ # Initializes a new Authenticator instance with the provided field, value, and password.
27
31
  def initialize(field:, value:, password:)
28
32
  @field = field
29
33
  @value = value
30
34
  @password = password
31
35
  end
32
36
 
33
- # Returns user upon successful authentication, otherwise returns nil.
37
+ # Returns an authenticated and enabled user or nil.
34
38
  def authenticated_user
35
- user if valid? && user&.authenticated?(password)
39
+ authenticated&.enabled if valid?
40
+ end
41
+
42
+ private
43
+
44
+ # Attempts to authenticate a user based on the provided field, value, and password.
45
+ # Returns user upon successful authentication, otherwise returns nil.
46
+ def authenticated
47
+ MinimalistAuthentication.configuration.user_model.authenticate_by(field => value, password:)
36
48
  end
37
49
 
38
50
  # Returns true if all the authentication attributes are present.
51
+ # Otherwise returns false.
39
52
  def valid?
40
53
  [field, value, password].all?(&:present?)
41
54
  end
42
-
43
- private
44
-
45
- def user
46
- @user ||= MinimalistAuthentication.configuration.user_model.active.find_by(field => value)
47
- end
48
55
  end
49
56
  end
@@ -51,10 +51,6 @@ module MinimalistAuthentication
51
51
  # Defaults to :new_session_path
52
52
  attr_accessor :logout_redirect_path
53
53
 
54
- # Email subject prefix for MinimalistAuthenticationMailer messages
55
- # Defaults to application name
56
- attr_accessor :email_prefix
57
-
58
54
  def initialize
59
55
  self.user_model_name = "::User"
60
56
  self.session_key = :user_id
@@ -64,20 +60,21 @@ module MinimalistAuthentication
64
60
  self.verify_email = true
65
61
  self.login_redirect_path = :root_path
66
62
  self.logout_redirect_path = :new_session_path
67
- self.email_prefix = default_email_prefix
68
63
  end
69
64
 
70
- # Returns the user_model class
71
- # Calling constantize on a string makes this work correctly with
72
- # the Spring application preloader gem.
73
- def user_model
74
- user_model_name.constantize
65
+ # Clear the user_model class
66
+ def clear_user_model
67
+ @user_model = nil
75
68
  end
76
69
 
77
- private
70
+ # Display deprecation warning for email_prefix
71
+ def email_prefix=(_)
72
+ MinimalistAuthentication.deprecator.warn("The #email_prefix configuration setting is no longer supported.")
73
+ end
78
74
 
79
- def default_email_prefix
80
- "[#{Rails.application.engine_name.delete_suffix('_application').titleize}]"
75
+ # Returns the user_model class
76
+ def user_model
77
+ @user_model ||= user_model_name.constantize
81
78
  end
82
79
  end
83
80
  end
@@ -9,6 +9,8 @@ module MinimalistAuthentication
9
9
  # use skip_before_action to open up specific actions
10
10
  before_action :authorization_required
11
11
 
12
+ helper MinimalistAuthentication::ApplicationHelper
13
+
12
14
  helper_method :current_user, :logged_in?, :authorized?
13
15
  end
14
16
 
@@ -19,9 +21,7 @@ module MinimalistAuthentication
19
21
  end
20
22
 
21
23
  def find_session_user
22
- return unless session_user_id
23
-
24
- MinimalistAuthentication.configuration.user_model.active.find_by(id: session_user_id)
24
+ MinimalistAuthentication.configuration.user_model.find_enabled(session_user_id)
25
25
  end
26
26
 
27
27
  def session_user_id
@@ -5,9 +5,31 @@ module MinimalistAuthentication
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- before_save :clear_email_verification, if: ->(user) { user.email_changed? }
8
+ generates_token_for :email_verification, expires_in: 1.hour do
9
+ email
10
+ end
9
11
 
10
- scope :email_verified, -> { where("LENGTH(email) > 2").where.not(email_verified_at: nil) }
12
+ before_save :clear_email_verification, if: :email_changed?
13
+
14
+ scope :with_verified_email, -> { where.not(email_verified_at: nil) }
15
+ end
16
+
17
+ module ClassMethods
18
+ def email_verified
19
+ MinimalistAuthentication.deprecator.warn(<<-MSG.squish)
20
+ Calling #email_verified is deprecated.
21
+ Call #with_verified_email instead.
22
+ MSG
23
+ with_verified_email
24
+ end
25
+
26
+ def find_by_verified_email(email:)
27
+ active.with_verified_email.find_by(email:)
28
+ end
29
+ end
30
+
31
+ def email_verified?
32
+ email.present? && email_verified_at.present?
11
33
  end
12
34
 
13
35
  def needs_email_set?
@@ -18,26 +40,22 @@ module MinimalistAuthentication
18
40
  email_verification_enabled? && email.present? && email_verified_at.blank?
19
41
  end
20
42
 
21
- def email_verified?
22
- email.present? && email_verified_at.present?
23
- end
24
-
25
43
  def verify_email(token)
26
- secure_update(token, email_verified_at: Time.zone.now)
44
+ touch(:email_verified_at) if token_owner?(:email_verification, token)
27
45
  end
28
46
 
29
47
  private
30
48
 
31
- def request_email_enabled?
32
- MinimalistAuthentication.configuration.request_email
49
+ def clear_email_verification
50
+ self.email_verified_at = nil
33
51
  end
34
52
 
35
53
  def email_verification_enabled?
36
54
  MinimalistAuthentication.configuration.verify_email
37
55
  end
38
56
 
39
- def clear_email_verification
40
- self.email_verified_at = nil
57
+ def request_email_enabled?
58
+ MinimalistAuthentication.configuration.request_email
41
59
  end
42
60
  end
43
61
  end
@@ -2,5 +2,10 @@
2
2
 
3
3
  module MinimalistAuthentication
4
4
  class Engine < ::Rails::Engine
5
+ isolate_namespace MinimalistAuthentication
6
+
7
+ config.to_prepare do
8
+ MinimalistAuthentication.configuration.clear_user_model
9
+ end
5
10
  end
6
11
  end
@@ -37,7 +37,7 @@ module MinimalistAuthentication
37
37
  end
38
38
 
39
39
  def authenticated_user
40
- @authenticated_user ||= MinimalistAuthentication::Authenticator.authenticated_user(user_params)
40
+ @authenticated_user ||= MinimalistAuthentication::Authenticator.authenticate(user_params)
41
41
  end
42
42
 
43
43
  def log_in_user
@@ -9,7 +9,11 @@ module MinimalistAuthentication
9
9
  GUEST_USER_EMAIL = "guest"
10
10
 
11
11
  included do
12
- has_secure_password validations: false
12
+ has_secure_password
13
+
14
+ generates_token_for :password_reset, expires_in: 1.hour do
15
+ password_salt.last(10)
16
+ end
13
17
 
14
18
  # Force validations for a blank password.
15
19
  attribute :password_required, :boolean, default: false
@@ -24,37 +28,69 @@ module MinimalistAuthentication
24
28
  validates(:email, presence: true, if: :validate_email_presence?)
25
29
 
26
30
  # Password validations
27
- validates(
28
- :password,
29
- confirmation: true,
30
- length: { minimum: :password_minimum, maximum: :password_maximum },
31
- presence: true,
32
- if: :validate_password?
33
- )
31
+ # Adds validations for minimum password length and exclusivity.
32
+ # has_secure_password adds validations for presence, maximum length, confirmation,
33
+ # and password_challenge.
34
+ validates :password, length: { minimum: :password_minimum }, if: :validate_password?
34
35
  validate :password_exclusivity, if: :password?
35
36
 
36
37
  # Active scope
37
- scope :active, ->(state = true) { where(active: state) }
38
- scope :inactive, -> { active(false) }
38
+ scope :active, ->(state = true) { where(active: state) }
39
+
40
+ delegate :password_minimum, to: :class
39
41
  end
40
42
 
41
43
  module ClassMethods
44
+ # Finds a user by their id and returns the user if they are enabled.
45
+ # Returns nil if the user is not found or not enabled.
46
+ def find_enabled(id)
47
+ find_by(id:)&.enabled if id.present?
48
+ end
49
+
50
+ def inactive
51
+ MinimalistAuthentication.deprecator.warn(<<-MSG.squish)
52
+ Calling #inactive is deprecated. Use #active(false) instead.
53
+ MSG
54
+ active(false)
55
+ end
56
+
42
57
  # Returns a frozen user with the email set to GUEST_USER_EMAIL.
43
58
  def guest
44
59
  new(email: GUEST_USER_EMAIL).freeze
45
60
  end
61
+
62
+ # Minimum password length
63
+ def password_minimum = 12
64
+ end
65
+
66
+ # Called after a user is authenticated to determine if the user object should be returned.
67
+ def enabled
68
+ self if enabled?
69
+ end
70
+
71
+ # Returns true if the user is enabled.
72
+ # Override this method in your user model to implement custom logic that determines if a user is eligible to log in.
73
+ def enabled?
74
+ active?
75
+ end
76
+
77
+ # Remove the has_secure_password password blank error if password is not required.
78
+ def errors
79
+ super.tap { |errors| errors.delete(:password, :blank) unless validate_password? }
46
80
  end
47
81
 
48
82
  # Returns true if the user is not active.
49
83
  def inactive?
84
+ MinimalistAuthentication.deprecator.warn("Calling #inactive? is deprecated.")
50
85
  !active?
51
86
  end
52
87
 
53
88
  # Returns true if password matches the hashed_password, otherwise returns false.
54
89
  def authenticated?(password)
90
+ MinimalistAuthentication.deprecator.warn(<<-MSG.squish)
91
+ Calling #authenticated? is deprecated. Use #authenticate instead.
92
+ MSG
55
93
  authenticate(password)
56
- rescue ::BCrypt::Errors::InvalidHash
57
- false
58
94
  end
59
95
 
60
96
  # Check if user is a guest based on their email attribute
@@ -62,17 +98,11 @@ module MinimalistAuthentication
62
98
  email == GUEST_USER_EMAIL
63
99
  end
64
100
 
101
+ # Sets #last_logged_in_at to the current time without updating the updated_at timestamp.
65
102
  def logged_in
66
- # Use update_column to avoid updated_on trigger
67
103
  update_column(:last_logged_in_at, Time.current)
68
104
  end
69
105
 
70
- # Minimum password length
71
- def password_minimum = 12
72
-
73
- # Maximum password length
74
- def password_maximum = 40
75
-
76
106
  # Checks for password presence
77
107
  def password?
78
108
  password.present?
@@ -87,6 +117,11 @@ module MinimalistAuthentication
87
117
  end
88
118
  end
89
119
 
120
+ # Return true if the user matches the owner of the provided token.
121
+ def token_owner?(purpose, token)
122
+ self.class.find_by_token_for(purpose, token) == self
123
+ end
124
+
90
125
  # Require password for active users that either do no have a password hash
91
126
  # stored OR are attempting to set a new password. Set **password_required**
92
127
  # to true to force validations even when the password field is blank.
@@ -4,51 +4,33 @@ module MinimalistAuthentication
4
4
  module VerifiableToken
5
5
  extend ActiveSupport::Concern
6
6
 
7
- TOKEN_EXPIRATION_HOURS = 6
8
-
9
- # generate secure verification_token and record generation time
10
- def regenerate_verification_token
11
- update_token
12
- end
13
-
14
- def secure_update(token, attributes)
15
- if matches_verification_token?(token)
16
- update(attributes) && clear_token
17
- else
18
- errors.add(:base, "Verification token check failed")
19
- false
20
- end
7
+ included do
8
+ MinimalistAuthentication.deprecator.warn(<<-MSG.squish)
9
+ MinimalistAuthentication::VerifiableToken is no longer required.
10
+ You can safely remove the include from your user model.
11
+ MSG
21
12
  end
22
13
 
23
- def matches_verification_token?(token)
24
- token.present? && verification_token_valid? && secure_match?(token)
14
+ def matches_verification_token?(_token)
15
+ MinimalistAuthentication.deprecator.warn(<<-MSG.squish)
16
+ Calling #matches_verification_token? is deprecated.
17
+ MSG
25
18
  end
26
19
 
27
- def verification_token_valid?
28
- return false if verification_token.blank? || verification_token_generated_at.blank?
29
-
30
- verification_token_generated_at > TOKEN_EXPIRATION_HOURS.hours.ago
31
- end
32
-
33
- private
34
-
35
- def clear_token
36
- update_token(token: nil, time: nil)
37
- end
38
-
39
- def update_token(token: self.class.generate_unique_secure_token, time: Time.now.utc)
40
- update!(
41
- verification_token: token,
42
- verification_token_generated_at: time
43
- )
20
+ def regenerate_verification_token
21
+ MinimalistAuthentication.deprecator.warn(<<-MSG.squish)
22
+ Calling #regenerate_verification_token is deprecated and no longer generates tokens.
23
+ Call #generate_token_for with an argument of :password_reset or
24
+ :email_verification instead.
25
+ MSG
44
26
  end
45
27
 
46
- # Compare the tokens in a time-constant manner, to mitigate timing attacks.
47
- def secure_match?(token)
48
- ActiveSupport::SecurityUtils.secure_compare(
49
- ::Digest::SHA256.hexdigest(token),
50
- ::Digest::SHA256.hexdigest(verification_token)
51
- )
28
+ def verification_token
29
+ MinimalistAuthentication.deprecator.warn(<<-MSG.squish)
30
+ Calling #verification_token is deprecated and no longer returns a valid token.
31
+ Call #generate_token_for with an argument of :password_reset or
32
+ :email_verification instead.
33
+ MSG
52
34
  end
53
35
  end
54
36
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MinimalistAuthentication
4
- VERSION = "3.0.0"
4
+ VERSION = "3.2.0"
5
5
  end
@@ -9,3 +9,13 @@ require "minimalist_authentication/email_verification"
9
9
  require "minimalist_authentication/controller"
10
10
  require "minimalist_authentication/sessions"
11
11
  require "minimalist_authentication/test_helper"
12
+
13
+ module MinimalistAuthentication
14
+ class << self
15
+ delegate :user_model, to: :configuration
16
+
17
+ def deprecator
18
+ @deprecator ||= ActiveSupport::Deprecation.new("4.0", name)
19
+ end
20
+ end
21
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minimalist_authentication
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Baldwin
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-11-03 00:00:00.000000000 Z
12
+ date: 2024-11-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bcrypt
@@ -37,14 +37,14 @@ dependencies:
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 6.0.0
40
+ version: 7.1.0
41
41
  type: :runtime
42
42
  prerelease: false
43
43
  version_requirements: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 6.0.0
47
+ version: 7.1.0
48
48
  description: A Rails authentication plugin that takes a minimalist approach. It is
49
49
  designed to be simple to understand, use, and modify for your application.
50
50
  email:
@@ -60,6 +60,7 @@ files:
60
60
  - app/controllers/emails_controller.rb
61
61
  - app/controllers/password_resets_controller.rb
62
62
  - app/controllers/passwords_controller.rb
63
+ - app/helpers/minimalist_authentication/application_helper.rb
63
64
  - app/mailers/application_mailer.rb
64
65
  - app/mailers/minimalist_authentication_mailer.rb
65
66
  - app/views/email_verifications/new.html.erb
@@ -94,7 +95,6 @@ licenses:
94
95
  - MIT
95
96
  metadata:
96
97
  homepage_uri: https://github.com/wwidea/minimalist_authentication
97
- source_code_uri: https://github.com/wwidea/minimalist_authentication
98
98
  rubygems_mfa_required: 'true'
99
99
  post_install_message:
100
100
  rdoc_options: []