shieldify 0.1.2.pre.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +375 -0
- data/Rakefile +3 -0
- data/app/controllers/users/access_controller.rb +16 -0
- data/app/controllers/users/emails/reset_passwords_controller.rb +30 -0
- data/app/controllers/users/emails_controller.rb +17 -0
- data/app/models/jwt_session.rb +5 -0
- data/lib/generators/shieldify/USAGE +8 -0
- data/lib/generators/shieldify/install_generator.rb +52 -0
- data/lib/generators/shieldify/templates/initializer.rb.tt +52 -0
- data/lib/generators/shieldify/templates/locales/en.shieldify.yml.tt +58 -0
- data/lib/generators/shieldify/templates/locales/es.shieldify.yml.tt +48 -0
- data/lib/generators/shieldify/templates/mailer_layouts/mailer.html.erb +10 -0
- data/lib/generators/shieldify/templates/mailer_layouts/mailer.text.erb +3 -0
- data/lib/generators/shieldify/templates/mailer_views/email_changed.html.erb +3 -0
- data/lib/generators/shieldify/templates/mailer_views/email_changed.text.erb +5 -0
- data/lib/generators/shieldify/templates/mailer_views/email_confirmation_instructions.html.erb +7 -0
- data/lib/generators/shieldify/templates/mailer_views/email_confirmation_instructions.text.erb +7 -0
- data/lib/generators/shieldify/templates/mailer_views/password_changed.html.erb +3 -0
- data/lib/generators/shieldify/templates/mailer_views/password_changed.text.erb +5 -0
- data/lib/generators/shieldify/templates/mailer_views/reset_email_password_instructions.html.erb +5 -0
- data/lib/generators/shieldify/templates/mailer_views/reset_email_password_instructions.text.erb +9 -0
- data/lib/generators/shieldify/templates/mailer_views/unlock_access_instructions.html.erb +4 -0
- data/lib/generators/shieldify/templates/mailer_views/unlock_access_instructions.text.erb +7 -0
- data/lib/generators/shieldify/templates/migration.rb.tt +28 -0
- data/lib/generators/shieldify/templates/model.rb.tt +2 -0
- data/lib/shieldify/controllers/helpers.rb +29 -0
- data/lib/shieldify/failure_app.rb +8 -0
- data/lib/shieldify/jwt_service.rb +158 -0
- data/lib/shieldify/mailer.rb +44 -0
- data/lib/shieldify/middleware/authentication.rb +27 -0
- data/lib/shieldify/middleware.rb +36 -0
- data/lib/shieldify/model_extensions.rb +73 -0
- data/lib/shieldify/models/email_authenticatable/confirmable.rb +159 -0
- data/lib/shieldify/models/email_authenticatable/registerable.rb +117 -0
- data/lib/shieldify/models/email_authenticatable.rb +41 -0
- data/lib/shieldify/railtie.rb +52 -0
- data/lib/shieldify/strategies/email.rb +48 -0
- data/lib/shieldify/strategies/jwt.rb +78 -0
- data/lib/shieldify/version.rb +3 -0
- data/lib/shieldify.rb +74 -0
- data/lib/tasks/shieldify_tasks.rake +4 -0
- metadata +163 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shieldify
|
4
|
+
module Models
|
5
|
+
module EmailAuthenticatable
|
6
|
+
# This module implements email confirmation using tokens. It generates confirmation tokens
|
7
|
+
# when registering or changing a user's email address, enabling confirmation through a secure process.
|
8
|
+
# Includes functionality to automatically send confirmation instructions and manage email confirmations.
|
9
|
+
# When changing an email, the new one is automatically marked as pending confirmation,
|
10
|
+
# requiring the user to verify their access to the new email to complete confirmation.
|
11
|
+
# Additionally, it provides methods to check the expiration of confirmation tokens.
|
12
|
+
#
|
13
|
+
# @example Including the module in a User model
|
14
|
+
# class User < ApplicationRecord
|
15
|
+
# include Shieldify::Models::EmailAuthenticatable::Confirmable
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# @see #confirm_email
|
19
|
+
# @see #regenerate_and_save_email_confirmation_token
|
20
|
+
# @see #send_email_confirmation_instructions
|
21
|
+
# @see #email_confirmation_token_expired?
|
22
|
+
# @see #pending_email_confirmation?
|
23
|
+
# @see #confirmed?
|
24
|
+
# @see .confirm_email_by_token
|
25
|
+
# @see .resend_email_confirmation_instructions_to
|
26
|
+
# @see .add_error_to_empty_user
|
27
|
+
module Confirmable
|
28
|
+
extend ActiveSupport::Concern
|
29
|
+
|
30
|
+
included do
|
31
|
+
# @!attribute [rw] skip_email_confirmation_callbacks
|
32
|
+
# @return [Boolean] Allows selectively skipping email confirmation callbacks.
|
33
|
+
attr_accessor :skip_email_confirmation_callbacks
|
34
|
+
|
35
|
+
before_save :generate_email_confirmation, unless: -> { skip_email_confirmation_callbacks? || !email_changed? }
|
36
|
+
after_save(
|
37
|
+
:send_email_confirmation_instructions,
|
38
|
+
unless: -> { skip_email_confirmation_callbacks? || !unconfirmed_email? }
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Confirms the email if there is a pending email confirmation.
|
43
|
+
# This method updates the user's email to the unconfirmed email and clears the confirmation tokens.
|
44
|
+
#
|
45
|
+
# @return [Boolean] true if the email was successfully confirmed, false otherwise
|
46
|
+
def confirm_email
|
47
|
+
return false unless pending_email_confirmation?
|
48
|
+
|
49
|
+
self.skip_email_confirmation_callbacks = true
|
50
|
+
|
51
|
+
self.email = unconfirmed_email
|
52
|
+
self.unconfirmed_email = nil
|
53
|
+
self.email_confirmation_token = nil
|
54
|
+
self.email_confirmation_token_generated_at = nil
|
55
|
+
|
56
|
+
save do |result|
|
57
|
+
self.skip_email_confirmation_callbacks = nil
|
58
|
+
|
59
|
+
result
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Regenerates the email confirmation token and saves the user record.
|
64
|
+
#
|
65
|
+
# @return [Boolean] true if the token was successfully regenerated and saved, false otherwise
|
66
|
+
def regenerate_and_save_email_confirmation_token
|
67
|
+
generate_email_confirmation_token && save
|
68
|
+
end
|
69
|
+
|
70
|
+
# Sends email confirmation instructions to the unconfirmed email.
|
71
|
+
#
|
72
|
+
# @return [void]
|
73
|
+
def send_email_confirmation_instructions
|
74
|
+
params = { user: self, email_to: unconfirmed_email, token: email_confirmation_token, action: :email_confirmation_instructions }
|
75
|
+
Shieldify::Mailer.with(params).base_mailer.deliver_now
|
76
|
+
end
|
77
|
+
|
78
|
+
# Checks if the email confirmation token has expired.
|
79
|
+
#
|
80
|
+
# @return [Boolean] true if the token has expired, false otherwise
|
81
|
+
def email_confirmation_token_expired?
|
82
|
+
return true unless email_confirmation_token_generated_at
|
83
|
+
|
84
|
+
email_confirmation_token_generated_at < 24.hours.ago
|
85
|
+
end
|
86
|
+
|
87
|
+
# Checks if there is a pending email confirmation.
|
88
|
+
#
|
89
|
+
# @return [Boolean] true if there is a pending email confirmation, false otherwise
|
90
|
+
def pending_email_confirmation?
|
91
|
+
unconfirmed_email.present? && email_confirmation_token.present?
|
92
|
+
end
|
93
|
+
|
94
|
+
# Checks if the email has been confirmed.
|
95
|
+
#
|
96
|
+
# @return [Boolean] true if the email is confirmed, false otherwise
|
97
|
+
def confirmed?
|
98
|
+
email.present?
|
99
|
+
end
|
100
|
+
|
101
|
+
def skip_email_confirmation_callbacks?
|
102
|
+
@skip_email_confirmation_callbacks
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def generate_email_confirmation
|
108
|
+
self.unconfirmed_email = email
|
109
|
+
self.email = email_was
|
110
|
+
|
111
|
+
generate_email_confirmation_token
|
112
|
+
end
|
113
|
+
|
114
|
+
def generate_email_confirmation_token
|
115
|
+
self.email_confirmation_token = SecureRandom.hex(16)
|
116
|
+
self.email_confirmation_token_generated_at = Time.current
|
117
|
+
end
|
118
|
+
|
119
|
+
class_methods do
|
120
|
+
def confirm_email_by_token(token)
|
121
|
+
user = find_by_email_confirmation_token(token)
|
122
|
+
|
123
|
+
return add_error_to_empty_user(:email_confirmation_token, :invalid) if user.blank?
|
124
|
+
|
125
|
+
if user.email_confirmation_token_expired?
|
126
|
+
msg = I18n.t('shieldify.models.email_authenticatable.confirmable.email_confirmation_token.errors.expired')
|
127
|
+
user.errors.add(:email_confirmation_token, msg)
|
128
|
+
|
129
|
+
return user
|
130
|
+
end
|
131
|
+
|
132
|
+
user.confirm_email
|
133
|
+
user
|
134
|
+
end
|
135
|
+
|
136
|
+
def resend_email_confirmation_instructions_to(email)
|
137
|
+
user = find_by_unconfirmed_email(email)
|
138
|
+
|
139
|
+
return add_error_to_empty_user(:unconfirmed_email, :not_found) if user.nil?
|
140
|
+
|
141
|
+
user.regenerate_and_save_email_confirmation_token
|
142
|
+
user
|
143
|
+
end
|
144
|
+
|
145
|
+
def add_error_to_empty_user(param, error)
|
146
|
+
user = new
|
147
|
+
|
148
|
+
user.errors.add(
|
149
|
+
param.to_sym,
|
150
|
+
I18n.t("shieldify.models.email_authenticatable.confirmable.#{param.to_sym}.errors.#{error.to_sym}")
|
151
|
+
)
|
152
|
+
|
153
|
+
user
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shieldify
|
4
|
+
module Models
|
5
|
+
module EmailAuthenticatable
|
6
|
+
# This module provides registration functionality for users, including email normalization,
|
7
|
+
# validation of email and password, and methods for registering a user and updating their
|
8
|
+
# email and password.
|
9
|
+
#
|
10
|
+
# @example Including the module in a User model
|
11
|
+
# class User < ApplicationRecord
|
12
|
+
# include Shieldify::Models::EmailAuthenticatable::Registerable
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# @see .register
|
16
|
+
# @see #update_password
|
17
|
+
# @see #update_email
|
18
|
+
# @see #send_email_changed_notification
|
19
|
+
# @see #send_password_changed_notification
|
20
|
+
module Registerable
|
21
|
+
extend ActiveSupport::Concern
|
22
|
+
|
23
|
+
included do
|
24
|
+
before_validation :normalize_email
|
25
|
+
|
26
|
+
validates :email, presence: true, if: -> { password.present? && new_record? }
|
27
|
+
validates :email, format: { with: Shieldify::Configuration.email_regexp }, if: -> { email.present? }
|
28
|
+
validates :email, uniqueness: true, if: -> { email.present? }
|
29
|
+
|
30
|
+
validates :password, presence: true, if: -> { email.present? && new_record? }
|
31
|
+
validate :password_complexity, if: -> { password.present? }
|
32
|
+
validates :password, length: { minimum: 8 }, if: -> { password.present? }
|
33
|
+
end
|
34
|
+
|
35
|
+
class_methods do
|
36
|
+
# Registers a new user with the given email and password.
|
37
|
+
#
|
38
|
+
# @param email [String] The email of the user.
|
39
|
+
# @param password [String] The password of the user.
|
40
|
+
# @param password_confirmation [String] The password confirmation.
|
41
|
+
# @return [User] The newly registered user.
|
42
|
+
def register(email:, password:, password_confirmation:)
|
43
|
+
user = new(email: email, password: password, password_confirmation: password_confirmation)
|
44
|
+
user.save
|
45
|
+
user
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Updates the user's password if the current password is valid.
|
50
|
+
#
|
51
|
+
# @param current_password [String] The current password of the user.
|
52
|
+
# @param new_password [String] The new password.
|
53
|
+
# @param password_confirmation [String] The new password confirmation.
|
54
|
+
# @return [User] The user with the updated password, or with errors if the update failed.
|
55
|
+
def update_password(current_password:, new_password:, password_confirmation:)
|
56
|
+
if authenticate(current_password)
|
57
|
+
if update(password: new_password, password_confirmation: password_confirmation)
|
58
|
+
send_password_changed_notification if Shieldify::Configuration.send_password_changed_notification
|
59
|
+
end
|
60
|
+
else
|
61
|
+
errors.add(
|
62
|
+
:current_password,
|
63
|
+
I18n.t("shieldify.models.email_authenticatable.registerable.password.errors.invalid")
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# Updates the user's email if the current password is valid.
|
71
|
+
#
|
72
|
+
# @param current_password [String] The current password of the user.
|
73
|
+
# @param new_email [String] The new email.
|
74
|
+
# @return [User] The user with the updated email, or with errors if the update failed.
|
75
|
+
def update_email(current_password:, new_email:)
|
76
|
+
if authenticate(current_password)
|
77
|
+
if update(email: new_email)
|
78
|
+
send_email_changed_notification if Shieldify::Configuration.send_email_changed_notification
|
79
|
+
end
|
80
|
+
else
|
81
|
+
errors.add(
|
82
|
+
:password,
|
83
|
+
I18n.t("shieldify.models.email_authenticatable.registerable.password.errors.invalid"))
|
84
|
+
end
|
85
|
+
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def send_email_changed_notification
|
92
|
+
Shieldify::Mailer.with(user: self, email_to: email, action: :email_changed).base_mailer.deliver_now
|
93
|
+
end
|
94
|
+
|
95
|
+
def send_password_changed_notification
|
96
|
+
Shieldify::Mailer.with(user: self, email_to: email, action: :password_changed).base_mailer.deliver_now
|
97
|
+
end
|
98
|
+
|
99
|
+
def normalize_email
|
100
|
+
self.email = email.downcase.strip if email.present?
|
101
|
+
end
|
102
|
+
|
103
|
+
def password_complexity
|
104
|
+
return if password.blank?
|
105
|
+
regex = Shieldify::Configuration.password_complexity
|
106
|
+
|
107
|
+
unless password.match?(regex)
|
108
|
+
errors.add(
|
109
|
+
:password,
|
110
|
+
I18n.t("shieldify.models.email_authenticatable.registerable.password_complexity.format")
|
111
|
+
)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shieldify
|
4
|
+
module Models
|
5
|
+
# This module provides email authentication functionality for users. It includes methods to authenticate
|
6
|
+
# users by their email and password, and uses `has_secure_password` for secure password handling.
|
7
|
+
#
|
8
|
+
# @example Including the module in a User model
|
9
|
+
# class User < ApplicationRecord
|
10
|
+
# include Shieldify::Models::EmailAuthenticatable
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# @see .authenticate_by_email
|
14
|
+
module EmailAuthenticatable
|
15
|
+
extend ActiveSupport::Concern
|
16
|
+
|
17
|
+
included do
|
18
|
+
# Adds methods to set and authenticate against a BCrypt password. This mechanism requires you to have a
|
19
|
+
# password_digest attribute.
|
20
|
+
has_secure_password(validations: false)
|
21
|
+
end
|
22
|
+
|
23
|
+
class_methods do
|
24
|
+
# Authenticates a user by their email and password.
|
25
|
+
#
|
26
|
+
# @param email [String] The email of the user.
|
27
|
+
# @param password [String] The password of the user.
|
28
|
+
# @return [User] The authenticated user if the credentials are correct, or a new user object with errors if not.
|
29
|
+
def authenticate_by_email(email:, password:)
|
30
|
+
user = find_by(email: email)
|
31
|
+
|
32
|
+
return user if user&.authenticate(password)
|
33
|
+
|
34
|
+
user ||= new
|
35
|
+
user.errors.add(:email, "invalid email or password")
|
36
|
+
user
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Shieldify
|
2
|
+
class Railtie < ::Rails::Railtie
|
3
|
+
initializer 'shieldify.add_routes' do |app|
|
4
|
+
app.routes.prepend do
|
5
|
+
get 'shfy/users/email/:token/confirm', to: 'users/emails#show', as: :users_email_confirmation
|
6
|
+
# post 'shfy/users/email/reset_password', to: 'users/emails/reset_passwords#create'
|
7
|
+
# put 'shfy/users/email/:token/reset_password', to: 'users/emails/reset_passwords#update'
|
8
|
+
# get 'shfy/users/access/:token/unlock', to: 'users/access#show'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer 'shieldify.configure_warden' do |app|
|
13
|
+
app.middleware.use Warden::Manager do |manager|
|
14
|
+
manager.strategies.add(:email, Shieldify::Strategies::Email)
|
15
|
+
manager.strategies.add(:jwt, Shieldify::Strategies::Jwt)
|
16
|
+
|
17
|
+
manager.default_strategies :email, :jwt
|
18
|
+
|
19
|
+
manager.default_strategies :email
|
20
|
+
manager.scope_defaults :default, store: false
|
21
|
+
manager.failure_app = ->(env) { Shieldify::FailureApp.call(env) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
initializer 'shieldify.insert_middleware', after: :load_config_initializers do |app|
|
26
|
+
app.middleware.insert_after Warden::Manager, Shieldify::Middleware
|
27
|
+
end
|
28
|
+
|
29
|
+
initializer 'shieldify.require' do
|
30
|
+
require_relative '../../app/models/jwt_session'
|
31
|
+
require_relative '../../app/controllers/users/emails_controller'
|
32
|
+
end
|
33
|
+
|
34
|
+
initializer 'shieldify.active_record' do
|
35
|
+
ActiveSupport.on_load(:active_record) do
|
36
|
+
include Shieldify::ModelExtensions
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
initializer 'shieldify.action_mailer' do |app|
|
41
|
+
ActiveSupport.on_load(:action_mailer) do
|
42
|
+
include app.routes.url_helpers
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
initializer 'shieldify.include_controller_helpers' do
|
47
|
+
ActiveSupport.on_load(:action_controller_api) do
|
48
|
+
include Shieldify::Controllers::Helpers
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Shieldify
|
2
|
+
module Strategies
|
3
|
+
class Email < Warden::Strategies::Base
|
4
|
+
def valid?
|
5
|
+
json_body['email'].present? && json_body['password'].present?
|
6
|
+
end
|
7
|
+
|
8
|
+
def authenticate!
|
9
|
+
user = User.authenticate_by_email(email: json_body['email'], password: json_body['password'])
|
10
|
+
return fail!("Unauthorized: #{user.errors.full_messages.join(", ")}") unless user.errors.empty?
|
11
|
+
|
12
|
+
handle_user_authentication(user)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def json_body
|
18
|
+
request.body.rewind
|
19
|
+
JSON.parse(request.body.read) rescue {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def handle_user_authentication(user)
|
23
|
+
JwtService.encode(user.id) do |success, token, jti, error|
|
24
|
+
if success
|
25
|
+
process_jwt_token(user, token, jti)
|
26
|
+
else
|
27
|
+
fail!("JWT token generation failed: #{error}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def process_jwt_token(user, token, jti)
|
33
|
+
jwt_token = user.jwt_sessions.create(jti: jti)
|
34
|
+
if jwt_token.persisted?
|
35
|
+
set_jwt_to_header(token)
|
36
|
+
success!(user)
|
37
|
+
else
|
38
|
+
fail!("Failed to save JWT token: #{jwt_token.errors.full_messages.join(", ")}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_jwt_to_header(token)
|
43
|
+
env['auth.jwt'] ||= {}
|
44
|
+
env['auth.jwt'] = token
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Shieldify
|
2
|
+
module Strategies
|
3
|
+
class Jwt < Warden::Strategies::Base
|
4
|
+
def valid?
|
5
|
+
request.env['HTTP_AUTHORIZATION'].present?
|
6
|
+
end
|
7
|
+
|
8
|
+
def authenticate!
|
9
|
+
token = extract_token_from_header
|
10
|
+
return fail!("Authorization token not provided") unless token
|
11
|
+
|
12
|
+
JwtService.decode(token) do |success, payload, error|
|
13
|
+
if success
|
14
|
+
authenticate_with_payload(payload)
|
15
|
+
else
|
16
|
+
fail!("Invalid token: #{error}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def extract_token_from_header
|
24
|
+
request.env['HTTP_AUTHORIZATION']&.split(' ')&.last
|
25
|
+
end
|
26
|
+
|
27
|
+
def authenticate_with_payload(payload)
|
28
|
+
user = find_user(payload['sub'])
|
29
|
+
return unless user
|
30
|
+
|
31
|
+
jwt_session = find_jwt_session(user, payload['jti'])
|
32
|
+
return unless jwt_session
|
33
|
+
|
34
|
+
if token_expired_halfway?(payload['exp'])
|
35
|
+
refresh_token(user, jwt_session)
|
36
|
+
else
|
37
|
+
success!(user)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_user(user_id)
|
42
|
+
user = User.find_by(id: user_id)
|
43
|
+
fail!("Invalid token: user not found") unless user
|
44
|
+
user
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_jwt_session(user, jti)
|
48
|
+
jwt_session = user.jwt_sessions.find_by(jti: jti)
|
49
|
+
fail!("Invalid token: session not found") unless jwt_session
|
50
|
+
jwt_session
|
51
|
+
end
|
52
|
+
|
53
|
+
def token_expired_halfway?(exp)
|
54
|
+
halfway_time = exp - (exp - Time.now.to_i) / 2
|
55
|
+
Time.now.to_i >= halfway_time
|
56
|
+
end
|
57
|
+
|
58
|
+
def refresh_token(user, jwt_session)
|
59
|
+
JwtService.encode(user.id) do |success, new_token, new_jti, error|
|
60
|
+
if success
|
61
|
+
jwt_session.update!(jti: new_jti)
|
62
|
+
set_jwt_to_header(new_token)
|
63
|
+
success!(user)
|
64
|
+
else
|
65
|
+
fail!("JWT token generation failed: #{error}")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_jwt_to_header(token)
|
71
|
+
env['auth.jwt'] ||= {}
|
72
|
+
env['auth.jwt'] = token
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
data/lib/shieldify.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require "warden"
|
2
|
+
require "shieldify/failure_app"
|
3
|
+
require "shieldify/model_extensions"
|
4
|
+
require "shieldify/controllers/helpers"
|
5
|
+
require "shieldify/strategies/email"
|
6
|
+
require "shieldify/strategies/jwt"
|
7
|
+
require "shieldify/middleware/authentication"
|
8
|
+
require "shieldify/middleware"
|
9
|
+
require "shieldify/version"
|
10
|
+
require "shieldify/railtie"
|
11
|
+
|
12
|
+
module Shieldify
|
13
|
+
class Configuration
|
14
|
+
include Singleton
|
15
|
+
|
16
|
+
# Default mailer sender.
|
17
|
+
mattr_accessor :mailer_sender
|
18
|
+
@@mailer_sender = "shieldify@example.com"
|
19
|
+
|
20
|
+
# Default mailer sender.
|
21
|
+
mattr_accessor :reply_to
|
22
|
+
@@reply_to = "shieldify@example.com"
|
23
|
+
|
24
|
+
# Email regex used to validate email formats.
|
25
|
+
mattr_accessor :email_regexp
|
26
|
+
@@email_regexp = URI::MailTo::EMAIL_REGEXP
|
27
|
+
|
28
|
+
# Password complexity regex
|
29
|
+
# Explanation of the regular expression:
|
30
|
+
# - (?=.*\d) ensures there is at least one digit
|
31
|
+
# - (?=.*[a-z]) ensures there is at least one lowercase letter
|
32
|
+
# - (?=.*[A-Z]) ensures there is at least one uppercase letter
|
33
|
+
# - (?=.*[@$#!%*?&]) ensures there is at least one of these special characters
|
34
|
+
# - \S{8,} ensures the password is at least 8 characters in length and contains no spaces
|
35
|
+
mattr_accessor :password_complexity
|
36
|
+
@@password_complexity = /\A(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@$#!%*?&])\S{6,}\z/
|
37
|
+
|
38
|
+
# Used to send notification to the original user email when their email is changed.
|
39
|
+
mattr_accessor :send_email_changed_notification
|
40
|
+
@@send_email_changed_notification = true
|
41
|
+
|
42
|
+
# Used to enable sending notification to user when their password is changed.
|
43
|
+
mattr_accessor :send_password_changed_notification
|
44
|
+
@@send_password_changed_notification = true
|
45
|
+
|
46
|
+
# Number of authentication tries before locking an account.
|
47
|
+
mattr_accessor :maximum_attempts
|
48
|
+
@@maximum_attempts = 20
|
49
|
+
|
50
|
+
# The parent mailer for internal mailers.
|
51
|
+
mattr_accessor :parent_mailer
|
52
|
+
@@parent_mailer = "ActionMailer::Base"
|
53
|
+
|
54
|
+
# JWT related
|
55
|
+
mattr_accessor :jwt_secret
|
56
|
+
@@jwt_secret = "whatever"
|
57
|
+
|
58
|
+
mattr_accessor :jwt_issuer
|
59
|
+
@@jwt_issuer = "Shieldify"
|
60
|
+
|
61
|
+
mattr_accessor :jwt_exp
|
62
|
+
@@jwt_exp = 24.hours.from_now.to_i
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.setup
|
66
|
+
yield Configuration.instance
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
require "shieldify/models/email_authenticatable"
|
71
|
+
require "shieldify/models/email_authenticatable/registerable"
|
72
|
+
require "shieldify/models/email_authenticatable/confirmable"
|
73
|
+
require "shieldify/jwt_service"
|
74
|
+
require "shieldify/mailer"
|