minimalist_authentication 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +49 -29
- data/app/controllers/email_verifications_controller.rb +5 -4
- data/app/controllers/emails_controller.rb +1 -1
- data/app/controllers/password_resets_controller.rb +21 -12
- data/app/controllers/passwords_controller.rb +13 -8
- data/app/helpers/minimalist_authentication/application_helper.rb +68 -0
- data/app/mailers/minimalist_authentication_mailer.rb +9 -13
- data/app/views/email_verifications/show.html.erb +1 -1
- data/app/views/emails/edit.html.erb +3 -3
- data/app/views/minimalist_authentication_mailer/update_password.html.erb +3 -1
- data/app/views/minimalist_authentication_mailer/update_password.text.erb +2 -2
- data/app/views/minimalist_authentication_mailer/verify_email.html.erb +9 -3
- data/app/views/minimalist_authentication_mailer/verify_email.text.erb +3 -3
- data/app/views/password_resets/new.html.erb +5 -6
- data/app/views/passwords/edit.html.erb +9 -7
- data/app/views/sessions/_form.html.erb +7 -7
- data/app/views/sessions/new.html.erb +3 -3
- data/config/locales/minimalist_authentication.en.yml +31 -9
- data/config/routes.rb +3 -7
- data/lib/minimalist_authentication/authenticator.rb +22 -15
- data/lib/minimalist_authentication/configuration.rb +10 -13
- data/lib/minimalist_authentication/controller.rb +3 -3
- data/lib/minimalist_authentication/email_verification.rb +29 -11
- data/lib/minimalist_authentication/engine.rb +5 -0
- data/lib/minimalist_authentication/sessions.rb +1 -1
- data/lib/minimalist_authentication/user.rb +54 -19
- data/lib/minimalist_authentication/verifiable_token.rb +21 -39
- data/lib/minimalist_authentication/version.rb +1 -1
- data/lib/minimalist_authentication.rb +10 -0
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba64bc8499dedd0f4fe8dfca813cc0c21e2d32759bd3b3b4dd8aac3c0e77605a
|
4
|
+
data.tar.gz: 566e4b373c274fd7843469d4d225560baf877aabde97658b1aa5a66f3c97f65e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
9
|
+
gem "minimalist_authentication"
|
10
10
|
```
|
11
11
|
|
12
|
-
And then
|
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.
|
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
|
-
|
75
|
-
|
76
|
-
|
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
|
91
|
-
|
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::
|
105
|
+
include MinimalistAuthentication::EmailVerification
|
100
106
|
end
|
101
107
|
```
|
102
108
|
|
103
|
-
Add the **
|
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
|
111
|
+
bin/rails generate migration AddEmailVerifiedAtToUsers email_verified_at:datetime
|
107
112
|
```
|
108
113
|
|
109
|
-
|
110
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
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
|
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
|
-
#
|
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.
|
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
|
@@ -5,30 +5,39 @@ class PasswordResetsController < ApplicationController
|
|
5
5
|
|
6
6
|
layout "sessions"
|
7
7
|
|
8
|
-
#
|
8
|
+
# Renders form for user to request a password reset
|
9
9
|
def new
|
10
|
-
|
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
|
16
|
-
user
|
17
|
-
|
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
|
26
|
-
|
28
|
+
def email
|
29
|
+
params.dig(:user, :email)
|
30
|
+
end
|
27
31
|
|
28
|
-
|
32
|
+
def send_update_password_email
|
33
|
+
MinimalistAuthenticationMailer.with(user:).update_password.deliver_now
|
29
34
|
end
|
30
35
|
|
31
|
-
def
|
32
|
-
|
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
|
-
|
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.
|
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
|
26
|
-
|
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
|
34
|
-
|
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
|
-
|
5
|
-
|
6
|
-
|
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
|
10
|
-
@
|
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
|
17
|
-
@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
|
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
<h2>Please update your email address</h2>
|
4
4
|
|
5
|
-
<%=
|
5
|
+
<%= form_with(model: current_user, url: email_path) do |form| %>
|
6
6
|
<%= form.text_field :email %>
|
7
|
-
<%= form.submit
|
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(
|
12
|
+
<%= link_to("Skip Email Update", dashboard_path) %>
|
@@ -1,3 +1,3 @@
|
|
1
|
-
<%= t(
|
1
|
+
<%= t(".opening") %>
|
2
2
|
|
3
|
-
<%= @
|
3
|
+
<%= @edit_password_url %>
|
@@ -1,5 +1,11 @@
|
|
1
|
-
<p
|
1
|
+
<p>
|
2
|
+
<%= t(".opening") %>
|
3
|
+
</p>
|
2
4
|
|
3
|
-
|
5
|
+
<p>
|
6
|
+
<%= link_to("Verify Email Address", @verify_email_url, rel: "noopener noreferrer", target: "_blank") %>
|
7
|
+
</p>
|
4
8
|
|
5
|
-
<p
|
9
|
+
<p>
|
10
|
+
<%= t(".closing") %>
|
11
|
+
</p>
|
@@ -1,13 +1,12 @@
|
|
1
|
-
<h1
|
1
|
+
<h1><%= t(".title") %></h1>
|
2
2
|
|
3
3
|
<p>
|
4
|
-
|
4
|
+
<%= t(".instructions") %>
|
5
5
|
</p>
|
6
6
|
|
7
|
-
<%=
|
7
|
+
<%= form_with scope: :user, url: password_reset_path do |form| %>
|
8
8
|
<div>
|
9
|
-
<%= form
|
10
|
-
<%= form.text_field :email %>
|
9
|
+
<%= ma_email_field(form) %>
|
11
10
|
</div>
|
12
|
-
<%= form.submit
|
11
|
+
<%= form.submit t(".submit") %>
|
13
12
|
<% end %>
|
@@ -1,15 +1,17 @@
|
|
1
|
-
<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
|
-
<%=
|
7
|
+
<%= form_with model: @user, url: password_path(token: @token) do |form| %>
|
6
8
|
<div>
|
7
|
-
<%= form.label
|
8
|
-
<%= form
|
9
|
+
<%= form.label :password %>
|
10
|
+
<%= ma_new_password_field(form) %>
|
9
11
|
</div>
|
10
12
|
<div>
|
11
|
-
<%= form.label
|
12
|
-
<%= form
|
13
|
+
<%= form.label :password_confirmation %>
|
14
|
+
<%= ma_confirm_password_field(form) %>
|
13
15
|
</div>
|
14
|
-
<%= form.submit
|
16
|
+
<%= form.submit t(".submit") %>
|
15
17
|
<% end %>
|
@@ -1,11 +1,11 @@
|
|
1
|
-
<%=
|
1
|
+
<%= form_with model: @user, url: session_path do |form| %>
|
2
2
|
<div>
|
3
|
-
<%= form.label
|
4
|
-
<%= form
|
3
|
+
<%= form.label :email %>
|
4
|
+
<%= ma_email_field(form) %>
|
5
5
|
</div>
|
6
|
-
<div>
|
7
|
-
<%= form.label
|
8
|
-
<%= form
|
6
|
+
<div style="margin-top: 8px;">
|
7
|
+
<%= form.label :password %>
|
8
|
+
<%= ma_password_field(form) %>
|
9
9
|
</div>
|
10
|
-
<%= form.submit
|
10
|
+
<%= form.submit t("sessions.new.submit") %>
|
11
11
|
<% end %>
|
@@ -1,3 +1,3 @@
|
|
1
|
-
<h1
|
2
|
-
<%= render partial:
|
3
|
-
<%=
|
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
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
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 :
|
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
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
37
|
+
# Returns an authenticated and enabled user or nil.
|
34
38
|
def authenticated_user
|
35
|
-
|
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
|
-
#
|
71
|
-
|
72
|
-
|
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
|
-
|
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
|
-
|
80
|
-
|
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
|
-
|
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
|
-
|
8
|
+
generates_token_for :email_verification, expires_in: 1.hour do
|
9
|
+
email
|
10
|
+
end
|
9
11
|
|
10
|
-
|
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
|
-
|
44
|
+
touch(:email_verified_at) if token_owner?(:email_verification, token)
|
27
45
|
end
|
28
46
|
|
29
47
|
private
|
30
48
|
|
31
|
-
def
|
32
|
-
|
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
|
40
|
-
|
57
|
+
def request_email_enabled?
|
58
|
+
MinimalistAuthentication.configuration.request_email
|
41
59
|
end
|
42
60
|
end
|
43
61
|
end
|
@@ -37,7 +37,7 @@ module MinimalistAuthentication
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def authenticated_user
|
40
|
-
@authenticated_user ||= MinimalistAuthentication::Authenticator.
|
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
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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,
|
38
|
-
|
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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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?(
|
24
|
-
|
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
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
@@ -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.
|
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-
|
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:
|
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:
|
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: []
|