shieldify 0.1.2.pre.alpha
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 +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,48 @@
|
|
1
|
+
es:
|
2
|
+
shieldify:
|
3
|
+
models:
|
4
|
+
email_authenticatable:
|
5
|
+
confirmable:
|
6
|
+
email_confirmation_token:
|
7
|
+
errors:
|
8
|
+
invalid: "inválido"
|
9
|
+
expired: "ha expirado"
|
10
|
+
unconfirmed_email:
|
11
|
+
errors:
|
12
|
+
not_found: "no encontrado"
|
13
|
+
email_confirmation_token_generated_at:
|
14
|
+
mailer:
|
15
|
+
email_confirmation_instructions:
|
16
|
+
subject: "Instrucciones de Confirmación de Email"
|
17
|
+
title: "Instrucciones de Confirmación de Email"
|
18
|
+
greeting: "Hola %{email},"
|
19
|
+
thanks: "Por favor confirma tu email haciendo clic en el siguiente enlace:"
|
20
|
+
confirm_account: "Confirmar email"
|
21
|
+
ignore: "Si no has solicitado esta confirmación, por favor ignora este correo."
|
22
|
+
reset_password_instructions:
|
23
|
+
subject: "Instrucciones para Restablecer Contraseña"
|
24
|
+
title: "Instrucciones para Restablecer Contraseña"
|
25
|
+
greeting: "Hola %{email},"
|
26
|
+
instructions: "Alguien ha solicitado un enlace para cambiar tu contraseña. Puedes hacerlo a través del enlace de abajo:"
|
27
|
+
change_password: "Cambiar mi contraseña"
|
28
|
+
ignore: "Si no solicitaste esto, por favor ignora este correo. Tu contraseña no cambiará."
|
29
|
+
link_expiration: "Este enlace expirará en %{expiration_hours} horas."
|
30
|
+
unlock_instructions:
|
31
|
+
subject: "Instrucciones para Desbloquear Cuenta"
|
32
|
+
title: "Instrucciones para Desbloquear Cuenta"
|
33
|
+
greeting: "Hola %{email},"
|
34
|
+
instructions: "Tu cuenta ha sido bloqueada debido a un número excesivo de intentos de inicio de sesión fallidos. Por favor, desbloquea tu cuenta haciendo clic en el siguiente enlace:"
|
35
|
+
unlock_account: "Desbloquear Cuenta"
|
36
|
+
ignore: "Si no has solicitado esto, por favor ignora este correo. Tu cuenta permanecerá segura."
|
37
|
+
email_changed:
|
38
|
+
subject: "Correo Electrónico Actualizado"
|
39
|
+
title: "Correo Electrónico Actualizado"
|
40
|
+
greeting: "Hola %{email},"
|
41
|
+
message: "Recibimos una solicitud para cambiar la dirección de correo electrónico asociada a tu cuenta a %{unconfirmed_email}."
|
42
|
+
ignore: "Si no hiciste este cambio, por favor contacta a soporte inmediatamente."
|
43
|
+
password_changed:
|
44
|
+
subject: "Contraseña Actualizada"
|
45
|
+
title: "Contraseña Actualizada"
|
46
|
+
greeting: "Hola %{email},"
|
47
|
+
message: "Esta es una notificación de que tu contraseña ha sido cambiada exitosamente."
|
48
|
+
advice: "Si no realizaste este cambio, por favor contacta a nuestro equipo de soporte inmediatamente."
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<h1><%= t('shieldify.mailer.email_confirmation_instructions.greeting', email: @user.unconfirmed_email) %></h1>
|
2
|
+
|
3
|
+
<p><%= t('shieldify.mailer.email_confirmation_instructions.thanks') %></p>
|
4
|
+
|
5
|
+
<p><%= link_to t('shieldify.mailer.email_confirmation_instructions.confirm_account'), users_email_confirmation_url(token: @token) %></p>
|
6
|
+
|
7
|
+
<p><%= t('shieldify.mailer.email_confirmation_instructions.ignore') %></p>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<%= t('shieldify.mailer.email_confirmation_instructions.greeting', email: @user.unconfirmed_email) %>
|
2
|
+
|
3
|
+
<%= t('shieldify.mailer.email_confirmation_instructions.thanks') %>
|
4
|
+
|
5
|
+
<%= users_email_confirmation_url(token: @token) %>
|
6
|
+
|
7
|
+
<%= t('shieldify.mailer.email_confirmation_instructions.ignore') %>
|
data/lib/generators/shieldify/templates/mailer_views/reset_email_password_instructions.html.erb
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
<h1><%= t('shieldify.mailer.reset_password_instructions.greeting', name: @user.name) %></h1>
|
2
|
+
<p><%= t('shieldify.mailer.reset_password_instructions.instructions') %></p>
|
3
|
+
<p><%= link_to t('shieldify.mailer.reset_password_instructions.change_password'), edit_password_url(@user.reset_password_token) %></p>
|
4
|
+
<p><%= t('shieldify.mailer.reset_password_instructions.link_expiration', expiration_hours: 24) %></p>
|
5
|
+
<p><%= t('shieldify.mailer.reset_password_instructions.ignore') %></p>
|
data/lib/generators/shieldify/templates/mailer_views/reset_email_password_instructions.text.erb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
<%= t('shieldify.mailer.reset_password_instructions.greeting', name: @user.name) %>
|
2
|
+
|
3
|
+
<%= t('shieldify.mailer.reset_password_instructions.instructions') %>
|
4
|
+
|
5
|
+
<%= edit_password_url(@user.reset_password_token) %>
|
6
|
+
|
7
|
+
<%= t('shieldify.mailer.reset_password_instructions.link_expiration', expiration_hours: 24) %>
|
8
|
+
|
9
|
+
<%= t('shieldify.mailer.reset_password_instructions.ignore') %>
|
@@ -0,0 +1,4 @@
|
|
1
|
+
<h1><%= t('shieldify.mailer.unlock_instructions.greeting', name: @user.name) %></h1>
|
2
|
+
<p><%= t('shieldify.mailer.unlock_instructions.instructions') %></p>
|
3
|
+
<p><%= link_to t('shieldify.mailer.unlock_instructions.unlock_account'), unlock_url(@user.unlock_token) %></p>
|
4
|
+
<p><%= t('shieldify.mailer.unlock_instructions.ignore') %></p>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ShieldifyCreateUsers < ActiveRecord::Migration<%= migration_version %>
|
4
|
+
def change
|
5
|
+
create_table :users do |t|
|
6
|
+
## Email registerable
|
7
|
+
t.string :email, default: ""
|
8
|
+
t.string :password_digest, default: ""
|
9
|
+
|
10
|
+
## Email confirmable
|
11
|
+
t.string :unconfirmed_email
|
12
|
+
t.string :email_confirmation_token
|
13
|
+
t.string :email_confirmation_token_generated_at
|
14
|
+
|
15
|
+
t.timestamps null: false
|
16
|
+
end
|
17
|
+
|
18
|
+
create_table :jwt_sessions do |t|
|
19
|
+
t.string :jti, null: false
|
20
|
+
t.references :user, null: false, foreign_key: true
|
21
|
+
|
22
|
+
t.timestamps
|
23
|
+
end
|
24
|
+
|
25
|
+
add_index :users, :email, unique: true
|
26
|
+
add_index :jwt_sessions, :jti, unique: true
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Shieldify
|
2
|
+
module Controllers
|
3
|
+
module Helpers
|
4
|
+
def current_user
|
5
|
+
warden.user
|
6
|
+
end
|
7
|
+
|
8
|
+
def user_signed_in?
|
9
|
+
!!current_user
|
10
|
+
end
|
11
|
+
|
12
|
+
def authenticate_user!
|
13
|
+
unless user_signed_in?
|
14
|
+
respond_to_unauthorized
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def warden
|
21
|
+
request.env['warden']
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond_to_unauthorized
|
25
|
+
render json: { error: 'No autorizado' }, status: :unauthorized
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'jwt'
|
2
|
+
|
3
|
+
class JwtService
|
4
|
+
@secret_key = Shieldify::Configuration.jwt_secret
|
5
|
+
@issuer = Shieldify::Configuration.jwt_issuer
|
6
|
+
@jwt_exp = Shieldify::Configuration.jwt_exp
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# Generates a new JWT token for a user using their unique identifier.
|
10
|
+
#
|
11
|
+
# @param user_id [Integer] The unique identifier for the user for whom the token is being generated.
|
12
|
+
#
|
13
|
+
# This method constructs a JWT payload containing several fields including the user's ID, the token's
|
14
|
+
# expiration time, and other standard JWT claims. The JWT is then encoded using the HS256 algorithm
|
15
|
+
# with a secret key stored in the service's configuration. The method handles encoding issues and returns
|
16
|
+
# a structured array containing the results of the token generation process.
|
17
|
+
#
|
18
|
+
# The method can be called with or without a block:
|
19
|
+
#
|
20
|
+
# Without a block, it returns an array with four elements:
|
21
|
+
# - [Boolean] `result`: true if the token is successfully generated, false if an error occurred.
|
22
|
+
# - [String, nil] `token`: the generated JWT token if successful, otherwise nil.
|
23
|
+
# - [String, nil] `jti`: the unique JWT ID if successful, otherwise nil.
|
24
|
+
# - [String, nil] `errors`: description of the error if token generation failed, otherwise nil.
|
25
|
+
#
|
26
|
+
# With a block, the block is yielded with the same four elements, allowing for inline handling:
|
27
|
+
#
|
28
|
+
# Usage Examples:
|
29
|
+
#
|
30
|
+
# Without a block:
|
31
|
+
# success, token, jti, error = JwtService.encode(user_id)
|
32
|
+
# if success
|
33
|
+
# puts "JWT generated successfully: #{token}, JTI: #{jti}"
|
34
|
+
# else
|
35
|
+
# puts "Error: #{error}"
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# With a block:
|
39
|
+
# JwtService.encode(user_id) do |success, token, jti, error|
|
40
|
+
# if success
|
41
|
+
# puts "JWT generated successfully: #{token}, JTI: #{jti}"
|
42
|
+
# else
|
43
|
+
# puts "Error: #{error}"
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# This method provides a reliable way to generate JWTs with a standard set of claims and handles
|
48
|
+
# any exceptions that may occur during the encoding process.
|
49
|
+
def encode(user_id)
|
50
|
+
begin
|
51
|
+
payload = jwt_payload(user_id)
|
52
|
+
jti = payload[:jti]
|
53
|
+
token = JWT.encode(payload, secret_key, 'HS256')
|
54
|
+
result = [true, token, jti, nil]
|
55
|
+
rescue StandardError => e
|
56
|
+
result = [false, nil, nil, e.message]
|
57
|
+
end
|
58
|
+
|
59
|
+
if block_given?
|
60
|
+
yield(*result)
|
61
|
+
else
|
62
|
+
result
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Decodes a JWT token to verify its authenticity and check if it is still valid.
|
67
|
+
#
|
68
|
+
# @param token [String] The JWT token to be decoded.
|
69
|
+
#
|
70
|
+
# This method uses JWT.decode to attempt to decode the token using a predefined secret key and issuer.
|
71
|
+
# It handles various errors that might occur during the decoding process, such as expiration or incorrect
|
72
|
+
# formatting of the token. It encapsulates the results and errors into a structured array format.
|
73
|
+
#
|
74
|
+
# The method can be called with or without a block:
|
75
|
+
#
|
76
|
+
# Without a block, it returns an array with three elements:
|
77
|
+
# - [Boolean] `result`: true if the token is successfully decoded, false if an error occurred.
|
78
|
+
# - [Hash, nil] `payload`: the decoded payload of the token if successful, otherwise nil.
|
79
|
+
# - [String, nil] `errors`: description of the error if decoding failed, otherwise nil.
|
80
|
+
#
|
81
|
+
# With a block, the block is yielded with the same three elements, allowing for inline handling:
|
82
|
+
#
|
83
|
+
# Usage Examples:
|
84
|
+
#
|
85
|
+
# Without a block:
|
86
|
+
# success, payload, error = JwtService.decode(token)
|
87
|
+
# if success
|
88
|
+
# puts "Decoded Payload: #{payload}"
|
89
|
+
# else
|
90
|
+
# puts "Error: #{error}"
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# With a block:
|
94
|
+
# JwtService.decode(token) do |success, payload, error|
|
95
|
+
# if success
|
96
|
+
# puts "Decoded Payload: #{payload}"
|
97
|
+
# else
|
98
|
+
# puts "Error: #{error}"
|
99
|
+
# end
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# This method ensures robust handling of JWTs by validating their integrity and relevance,
|
103
|
+
# adhering to the security settings defined by the secret_key and issuer configuration.
|
104
|
+
def decode(token)
|
105
|
+
decoded_token = JWT.decode(token, secret_key, true, decode_options).first
|
106
|
+
result = [true, decoded_token, nil] # result: true (success), payload: decoded_token, errors: nil
|
107
|
+
rescue *jwt_exceptions => e
|
108
|
+
result = [false, nil, e.message]
|
109
|
+
rescue => e
|
110
|
+
result = [false, nil, "Unexpected error: #{e.message}"]
|
111
|
+
ensure
|
112
|
+
if block_given?
|
113
|
+
yield(*result)
|
114
|
+
else
|
115
|
+
result
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def decode_options
|
122
|
+
{ algorithm: 'HS256', verify_iss: true, iss: issuer, verify_expiration: true }
|
123
|
+
end
|
124
|
+
|
125
|
+
def jwt_payload(user_id)
|
126
|
+
{
|
127
|
+
sub: user_id,
|
128
|
+
exp: jwt_exp,
|
129
|
+
nbf: Time.now.to_i,
|
130
|
+
iss: issuer,
|
131
|
+
jti: SecureRandom.hex,
|
132
|
+
iat: Time.now.to_i
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
def jwt_exceptions
|
137
|
+
[
|
138
|
+
JWT::ExpiredSignature,
|
139
|
+
JWT::InvalidIssuerError,
|
140
|
+
JWT::DecodeError,
|
141
|
+
JWT::VerificationError,
|
142
|
+
JWT::InvalidIatError
|
143
|
+
]
|
144
|
+
end
|
145
|
+
|
146
|
+
def secret_key
|
147
|
+
@secret_key
|
148
|
+
end
|
149
|
+
|
150
|
+
def issuer
|
151
|
+
@issuer
|
152
|
+
end
|
153
|
+
|
154
|
+
def jwt_exp
|
155
|
+
@jwt_exp
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shieldify
|
4
|
+
class Mailer < Shieldify::Configuration.parent_mailer.constantize
|
5
|
+
layout 'layouts/shieldify/mailer'
|
6
|
+
|
7
|
+
default(
|
8
|
+
from: Shieldify::Configuration.mailer_sender,
|
9
|
+
reply_to: Shieldify::Configuration.reply_to
|
10
|
+
)
|
11
|
+
|
12
|
+
def base_mailer
|
13
|
+
initialize_email_resources(params)
|
14
|
+
|
15
|
+
mail(define_headers)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def define_headers
|
21
|
+
headers = {
|
22
|
+
to: email_to,
|
23
|
+
subject: define_subject,
|
24
|
+
template_path: "shieldify/mailer",
|
25
|
+
template_name: action
|
26
|
+
}
|
27
|
+
|
28
|
+
headers.store(:reply_to, reply_to) if instance_variable_defined?(:@reply_to)
|
29
|
+
headers.store(:from, from) if instance_variable_defined?(:@from)
|
30
|
+
headers
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize_email_resources(options)
|
34
|
+
options.each do |key, value|
|
35
|
+
self.class.send(:attr_accessor, key)
|
36
|
+
instance_variable_set("@#{key}", value)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def define_subject
|
41
|
+
I18n.t("shieldify.mailer.#{action}.subject")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Shieldify
|
2
|
+
class Middleware
|
3
|
+
class Authentication
|
4
|
+
attr_reader :app
|
5
|
+
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
env['warden'].authenticate!(:email, :jwt)
|
12
|
+
|
13
|
+
status, headers, response = app.call(env)
|
14
|
+
headers = headers_with_token(env, headers)
|
15
|
+
[status, headers, response]
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def headers_with_token(env, headers)
|
21
|
+
token = env["auth.jwt"]
|
22
|
+
headers['Authorization'] = "Bearer #{token}"
|
23
|
+
headers
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Shieldify
|
2
|
+
class Middleware
|
3
|
+
attr_reader :app, :request
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
@request = Rack::Request.new(env)
|
11
|
+
|
12
|
+
if should_authenticate?
|
13
|
+
builder = Rack::Builder.new
|
14
|
+
builder.use(Shieldify::Middleware::Authentication)
|
15
|
+
builder.run(app)
|
16
|
+
builder.call(env)
|
17
|
+
else
|
18
|
+
app.call(env)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def should_authenticate?
|
25
|
+
should_authenticate_by_email? || should_authenticate_by_jwt?
|
26
|
+
end
|
27
|
+
|
28
|
+
def should_authenticate_by_email?
|
29
|
+
request.path.end_with?('/shfy/login')
|
30
|
+
end
|
31
|
+
|
32
|
+
def should_authenticate_by_jwt?
|
33
|
+
request.env['HTTP_AUTHORIZATION'].present?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shieldify
|
4
|
+
# This module provides extensions for models to include Shieldify functionality.
|
5
|
+
# It allows dynamic inclusion of modules and submodules for various authentication features.
|
6
|
+
#
|
7
|
+
# The primary purpose of this module is to simplify the process of adding authentication
|
8
|
+
# capabilities to models in a Rails application. By including this module,
|
9
|
+
# developers can easily integrate functionalities such as email-based authentication,
|
10
|
+
# user registration, email confirmation, and more, depending on the modules and submodules
|
11
|
+
# specified.
|
12
|
+
#
|
13
|
+
# This module uses ActiveSupport::Concern to extend the including model with class methods
|
14
|
+
# and associations necessary for managing JWT sessions and other authentication-related tasks.
|
15
|
+
#
|
16
|
+
# @example Including the module in a User model
|
17
|
+
# # To use this module, include it in your model (typically a User model).
|
18
|
+
# # Then, use the `shieldify` method to specify the desired modules and submodules.
|
19
|
+
# # In this example, the User model is being extended with email authentication,
|
20
|
+
# # and two submodules: registerable and confirmable.
|
21
|
+
# class User < ApplicationRecord
|
22
|
+
# include Shieldify::ModelExtensions
|
23
|
+
#
|
24
|
+
# # The `shieldify` method is called with a hash where the key is the main module
|
25
|
+
# # (:email_authenticatable) and the value is an array of submodules
|
26
|
+
# # (%i[registerable confirmable]).
|
27
|
+
# # This will dynamically include the corresponding modules and submodules
|
28
|
+
# # from the Shieldify::Models namespace into the User model.
|
29
|
+
# shieldify email_authenticatable: %i[registerable confirmable]
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# @see Shieldify::ModelExtensions#shieldify
|
33
|
+
module ModelExtensions
|
34
|
+
extend ActiveSupport::Concern
|
35
|
+
|
36
|
+
class_methods do
|
37
|
+
# Dynamically includes modules. Accepts a hash where the keys are symbols
|
38
|
+
# of main modules and the values are arrays of submodules to include.
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# shieldify email_authenticatable: %i[registerable confirmable]
|
42
|
+
#
|
43
|
+
# This method is intended to be used within a user class or any other class
|
44
|
+
# that acts as a user.
|
45
|
+
#
|
46
|
+
# @param modules [Hash<Symbol, Array<Symbol>>] A hash where the keys are main modules and the values are arrays of submodules to include.
|
47
|
+
# @return [void]
|
48
|
+
def shieldify(modules)
|
49
|
+
modules.each do |parent, submodules|
|
50
|
+
include_parent_module(parent)
|
51
|
+
|
52
|
+
submodules.each do |submodule|
|
53
|
+
include_submodule(parent, submodule)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
has_many :jwt_sessions, dependent: :destroy
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Includes the parent module based on the provided symbol
|
63
|
+
def include_parent_module(parent_module)
|
64
|
+
include "Shieldify::Models::#{parent_module.to_s.camelize}".constantize
|
65
|
+
end
|
66
|
+
|
67
|
+
# Includes a specific submodule within a parent module
|
68
|
+
def include_submodule(parent_module, submodule)
|
69
|
+
include "Shieldify::Models::#{parent_module.to_s.camelize}::#{submodule.to_s.camelize}".constantize
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|