lesli_shield 1.0.3 → 1.0.4

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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/lesli_shield/confirmations.css +18732 -0
  3. data/app/assets/stylesheets/lesli_shield/passwords.css +2 -238
  4. data/app/assets/stylesheets/lesli_shield/registrations.css +2 -238
  5. data/app/assets/stylesheets/lesli_shield/sessions.css +2 -238
  6. data/app/controllers/lesli_shield/dashboards_controller.rb +1 -8
  7. data/app/controllers/lesli_shield/invites_controller.rb +80 -0
  8. data/app/controllers/lesli_shield/role/actions_controller.rb +32 -20
  9. data/app/controllers/lesli_shield/roles_controller.rb +16 -8
  10. data/app/controllers/lesli_shield/sessions_controller.rb +5 -8
  11. data/app/controllers/lesli_shield/user/roles_controller.rb +62 -0
  12. data/app/controllers/lesli_shield/users_controller.rb +57 -20
  13. data/app/controllers/users/confirmations_controller.rb +42 -8
  14. data/app/controllers/users/passwords_controller.rb +52 -37
  15. data/app/controllers/users/registrations_controller.rb +2 -8
  16. data/app/controllers/users/sessions_controller.rb +57 -50
  17. data/app/helpers/lesli_shield/invites_helper.rb +4 -0
  18. data/app/helpers/lesli_shield/user/roles_helper.rb +4 -0
  19. data/app/interfaces/lesli_shield/authorization_interface.rb +8 -2
  20. data/app/mailers/lesli_shield/devise_mailer.rb +98 -0
  21. data/app/mailers/lesli_shield/invitation.html.erb +23 -0
  22. data/app/models/concerns/lesli_shield/user_security.rb +222 -0
  23. data/app/models/lesli_shield/account.rb +1 -1
  24. data/app/models/lesli_shield/dashboard.rb +1 -4
  25. data/app/models/lesli_shield/invite.rb +24 -0
  26. data/{lib/vue/confirmations.js → app/models/lesli_shield/role/action.rb} +17 -10
  27. data/{db/migrate/v1/0801003010_create_lesli_shield_dashboards.rb → app/models/lesli_shield/role/privilege.rb} +5 -4
  28. data/app/models/lesli_shield/user/role.rb +8 -0
  29. data/app/models/lesli_shield/user/session.rb +80 -0
  30. data/app/services/lesli_shield/invite_service.rb +43 -0
  31. data/app/services/lesli_shield/role_action_service.rb +118 -0
  32. data/app/services/lesli_shield/role_privilege_service.rb +112 -0
  33. data/app/{operators/lesli_shield/user_registration_operator.rb → services/lesli_shield/user_registration_service.rb} +26 -29
  34. data/app/services/lesli_shield/user_session_service.rb +78 -0
  35. data/app/services/lesli_shield/user_validator_service.rb +221 -0
  36. data/app/views/devise/confirmations/show.html.erb +4 -6
  37. data/app/views/devise/passwords/edit.html.erb +1 -2
  38. data/app/views/devise/passwords/new.html.erb +1 -1
  39. data/app/views/devise/registrations/new.html.erb +5 -4
  40. data/app/views/devise/sessions/new.html.erb +3 -2
  41. data/app/views/devise/shared/_application-devise-simple.erb +59 -0
  42. data/app/views/devise/shared/_application-devise.html.erb +76 -0
  43. data/app/views/lesli_shield/dashboards/_component-calendar.html.erb +1 -0
  44. data/app/views/lesli_shield/dashboards/_component-chart-bar.html.erb +6 -0
  45. data/app/views/lesli_shield/dashboards/_component-chart-line.html.erb +8 -0
  46. data/app/views/lesli_shield/dashboards/_component-count.html.erb +1 -0
  47. data/app/views/lesli_shield/dashboards/_component-date.html.erb +1 -0
  48. data/app/views/lesli_shield/dashboards/_component-weather.html.erb +1 -0
  49. data/app/views/lesli_shield/invites/_form.html.erb +10 -0
  50. data/app/views/lesli_shield/invites/_invite.html.erb +2 -0
  51. data/app/views/lesli_shield/invites/edit.html.erb +12 -0
  52. data/app/views/lesli_shield/invites/index.html.erb +66 -0
  53. data/{db/migrate/v1/0801001710_create_lesli_shield_settings.rb → app/views/lesli_shield/invites/new.html.erb} +9 -10
  54. data/{lib/vue/apps/dashboards/components/engine-version.vue → app/views/lesli_shield/invites/show.html.erb} +26 -43
  55. data/app/views/lesli_shield/partials/_navigation.html.erb +2 -4
  56. data/app/views/lesli_shield/{roles/_form-privileges.html.erb → role/actions/_form.html.erb} +5 -30
  57. data/app/views/lesli_shield/role/actions/index.html.erb +14 -0
  58. data/app/views/lesli_shield/roles/index.html.erb +2 -6
  59. data/app/views/lesli_shield/roles/new.html.erb +0 -11
  60. data/app/views/lesli_shield/roles/show.html.erb +5 -8
  61. data/app/views/lesli_shield/user/roles/_form.html.erb +17 -0
  62. data/app/views/lesli_shield/user/roles/_role.html.erb +2 -0
  63. data/app/views/lesli_shield/user/roles/edit.html.erb +12 -0
  64. data/app/views/lesli_shield/user/roles/index.html.erb +16 -0
  65. data/app/views/lesli_shield/user/roles/new.html.erb +11 -0
  66. data/app/views/lesli_shield/user/roles/show.html.erb +10 -0
  67. data/app/views/lesli_shield/users/{_viewer-activities.html.erb → _activities-viewer.html.erb} +2 -4
  68. data/app/views/lesli_shield/users/_information-card.html.erb +3 -3
  69. data/app/views/lesli_shield/users/_management-privileges.html.erb +74 -0
  70. data/app/views/lesli_shield/users/_management-security.html.erb +5 -0
  71. data/app/views/lesli_shield/users/index.html.erb +3 -7
  72. data/app/views/lesli_shield/users/new.html.erb +5 -11
  73. data/app/views/lesli_shield/users/show.html.erb +7 -5
  74. data/config/initializers/devise.rb +305 -304
  75. data/config/locales/translations.en.yml +4 -1
  76. data/config/locales/translations.es.yml +4 -1
  77. data/config/locales/translations.it.yml +4 -1
  78. data/config/routes.rb +7 -8
  79. data/db/migrate/v1/0801100210_create_lesli_shield_role_actions.rb +48 -0
  80. data/db/migrate/v1/0801100410_create_lesli_shield_role_privileges.rb +45 -0
  81. data/db/migrate/v1/0801110110_create_lesli_shield_user_roles.rb +43 -0
  82. data/db/migrate/v1/0801111210_create_lesli_shield_user_sessions.rb +56 -0
  83. data/db/migrate/v1/0801120110_create_lesli_shield_invites.rb +49 -0
  84. data/lib/lesli_shield/router.rb +21 -0
  85. data/lib/lesli_shield/version.rb +2 -2
  86. data/lib/lesli_shield.rb +1 -1
  87. data/lib/scss/confirmations.scss +24 -24
  88. data/lib/tasks/lesli_shield_tasks.rake +1 -1
  89. data/readme.md +59 -20
  90. metadata +57 -33
  91. data/app/controllers/lesli_shield/dashboard/components_controller.rb +0 -60
  92. data/app/models/lesli_shield/dashboard/component.rb +0 -18
  93. data/app/views/lesli_shield/dashboards/edit.html.erb +0 -1
  94. data/app/views/lesli_shield/dashboards/index.html.erb +0 -9
  95. data/app/views/lesli_shield/dashboards/new.html.erb +0 -1
  96. data/app/views/lesli_shield/dashboards/show.html.erb +0 -1
  97. data/app/views/lesli_shield/roles/_session.html.erb +0 -2
  98. data/app/views/lesli_shield/roles/edit.html.erb +0 -12
  99. data/app/views/lesli_shield/roles/update.turbo_stream.erb +0 -3
  100. data/app/views/lesli_shield/users/update.turbo_stream.erb +0 -3
  101. data/lib/lesli_shield/routing.rb +0 -23
  102. data/lib/vue/application.js +0 -83
  103. data/lib/vue/apps/sessions/index.vue +0 -50
  104. data/lib/vue/passwords.js +0 -137
  105. data/lib/vue/registrations.js +0 -144
  106. data/lib/vue/sessions.js +0 -148
  107. data/lib/vue/stores/sessions.js +0 -43
  108. data/lib/vue/stores/translations.json +0 -162
  109. /data/app/views/lesli_shield/roles/{_form-information.html.erb → _form.html.erb} +0 -0
  110. /data/db/migrate/v1/{0801120310_create_lesli_shield_user_shortcuts.rb → 0801111010_create_lesli_shield_user_shortcuts.rb} +0 -0
  111. /data/db/migrate/v1/{0801120410_create_lesli_shield_user_tokens.rb → 0801111110_create_lesli_shield_user_tokens.rb} +0 -0
@@ -1,4 +1,36 @@
1
1
  # frozen_string_literal: true
2
+
3
+ =begin
4
+
5
+ Lesli
6
+
7
+ Copyright (c) 2026, Lesli Technologies, S. A.
8
+
9
+ This program is free software: you can redistribute it and/or modify
10
+ it under the terms of the GNU General Public License as published by
11
+ the Free Software Foundation, either version 3 of the License, or
12
+ (at your option) any later version.
13
+
14
+ This program is distributed in the hope that it will be useful,
15
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ GNU General Public License for more details.
18
+
19
+ You should have received a copy of the GNU General Public License
20
+ along with this program. If not, see http://www.gnu.org/licenses/.
21
+
22
+ Lesli · Ruby on Rails SaaS Development Framework.
23
+
24
+ Made with ♥ by LesliTech
25
+ Building a better future, one line of code at a time.
26
+
27
+ @contact hello@lesli.tech
28
+ @website https://www.lesli.tech
29
+ @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
30
+
31
+ // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
32
+ // ·
33
+ =end
2
34
  class Users::ConfirmationsController < Devise::ConfirmationsController
3
35
 
4
36
  def show
@@ -23,22 +55,24 @@ class Users::ConfirmationsController < Devise::ConfirmationsController
23
55
  end
24
56
 
25
57
  # register a log with a validation atempt for the user
26
- activity = user.activities.create({ title: "user_confirmation", description: "Confirmation process started" })
58
+ log = user.log(engine: LesliShield, source: self.class.name, action: action_name, operation: "user_confirmation", description: "Confirmation process started")
27
59
 
28
-
29
- registration_operator = LesliShield::UserRegistrationOperator.new(user)
60
+ # create a new instance of the registration service
61
+ registration_service = LesliShield::UserRegistrationService.new(user)
30
62
 
31
63
  # confirm the user
32
- registration_operator.confirm
64
+ registration_service.confirm
65
+
66
+ # send a welcome email to user as is confirmed
67
+ LesliShield::DeviseMailer.with(user: resource).welcome.deliver_later
33
68
 
34
69
  # let the user knows that the confirmation is done
35
70
  flash[:success] = I18n.t("core.users/confirmations.messages_success_email_updated")
36
71
 
37
- # if new account, launch account onboarding in another thread,
38
- # so the user can continue with the registration process
39
- registration_operator.create_account if user.account.blank?
40
- #Thread.new { registration_operator.create_account } if user.account.blank?
72
+ # setup the new account
73
+ registration_service.create_account if user.account.blank?
41
74
 
75
+ log.update(description: 'User confirmed successfully') if defined?(LesliAudit)
42
76
  end
43
77
 
44
78
 
@@ -1,66 +1,81 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Users::PasswordsController < Devise::PasswordsController
3
+ =begin
4
4
 
5
- # Sends an email with a token, so the user can reset their password
6
- def create
7
- begin
5
+ Lesli
8
6
 
9
- if params[:user].blank?
10
- #Account::Activity.log("core", "/password/create", "password_creation_failed", "no_valid_email")
11
- raise(I18n.t("core.shared.messages_warning_user_not_found"))
12
- end
7
+ Copyright (c) 2026, Lesli Technologies, S. A.
13
8
 
14
- if params[:user][:email].blank?
15
- #Account::Activity.log("core", "/password/create", "password_creation_failed", "no_valid_email")
16
- raise(I18n.t("core.shared.messages_warning_user_not_found"))
17
- end
9
+ This program is free software: you can redistribute it and/or modify
10
+ it under the terms of the GNU General Public License as published by
11
+ the Free Software Foundation, either version 3 of the License, or
12
+ (at your option) any later version.
18
13
 
19
- user = Lesli::User.find_by(:email => params[:user][:email])
14
+ This program is distributed in the hope that it will be useful,
15
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ GNU General Public License for more details.
20
18
 
21
- if user.blank?
22
- # Account::Activity.log("core", "/password/create", "password_creation_failed", "no_valid_email", {
23
- # email: (params[:user][:email] || "")
24
- # })
25
- raise(I18n.t("core.shared.messages_warning_user_not_found"))
26
- end
19
+ You should have received a copy of the GNU General Public License
20
+ along with this program. If not, see http://www.gnu.org/licenses/.
27
21
 
28
- unless user.active
29
- user.activities.create({title: "password_creation_failed", description: "user_not_active"})
30
- # Account::Activity.log("core", "/password/create", "password_creation_failed", "user_not_active")
31
- raise(I18n.t("core.users/passwords.messages_danger_inactive_user"))
32
- end
22
+ Lesli · Ruby on Rails SaaS Development Framework.
23
+
24
+ Made with by LesliTech
25
+ Building a better future, one line of code at a time.
26
+
27
+ @contact hello@lesli.tech
28
+ @website https://www.lesli.tech
29
+ @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
30
+
31
+ // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
32
+ // ·
33
+ =end
34
+
35
+ class Users::PasswordsController < Devise::PasswordsController
36
+
37
+ # Sends an email with a token, so the user can reset their password
38
+ def create
39
+ self.resource = resource_class.send_reset_password_instructions(resource_params)
40
+
41
+ user = self.resource
33
42
 
34
- token = user.generate_password_reset_token
43
+ if successfully_sent?(resource)
35
44
 
36
- user.activities.create({ title: "password_create", description: "Password reset instructions sent" })
45
+ user.log(
46
+ :engine => LesliShield,
47
+ :source => self.class.name,
48
+ :action => action_name,
49
+ :operation => 'password_reset',
50
+ :description => 'Reset password instructions sent'
51
+ )
37
52
 
38
- Lesli::DeviseMailer.reset_password_instructions(user, token).deliver_now
39
53
  success(I18n.t("core.users/passwords.messages_success"))
40
54
  redirect_to(new_user_password_path)
41
- rescue => exception
42
- #Honeybadger.notify(exception)
43
- danger(exception.message)
55
+ else
56
+ #respond_with(resource)
57
+ danger("Error sending reset password instructions")
44
58
  redirect_to(new_user_password_path)
45
59
  end
46
60
  end
47
61
 
48
62
  def update
49
- super do |resource|
63
+ super do |user|
50
64
 
51
- logs = resource.activities.new({ title: "password_reset", description:"atempt" })
65
+ logs = user.log(engine: LesliShield, source: self.class.name, action: action_name, operation: 'password_update', description:"Password update attempt")
52
66
 
53
67
  # check if password update was ok
54
- if resource.errors.empty?
68
+ if user.errors.empty?
55
69
 
56
70
  # reset password expiration due the user just updated his password
57
- if resource.has_expired_password?
58
- resource.update(password_expiration_at: nil)
71
+ if user.has_expired_password?
72
+ user.update(password_expiration_at: nil)
59
73
  end
60
74
 
61
- logs.update({ description: "successful" })
75
+ logs&.update(description: "Password update successful")
62
76
  else
63
- logs.update({ description: resource.errors.full_messages.to_sentence })
77
+ danger(user.errors.full_messages.to_sentence)
78
+ logs&.update(description: resource.errors.full_messages.to_sentence)
64
79
  end
65
80
  end
66
81
  end
@@ -43,6 +43,7 @@ class Users::RegistrationsController < Devise::RegistrationsController
43
43
 
44
44
  def create
45
45
  begin
46
+
46
47
  # Check if instance allow multi-account
47
48
  if !Lesli.config.security.dig(:allow_registration)
48
49
  raise(I18n.t("core.users/registrations.messages_error_registration_not_allowed"))
@@ -51,16 +52,9 @@ class Users::RegistrationsController < Devise::RegistrationsController
51
52
  # build new user
52
53
  user = build_resource(sign_up_params)
53
54
 
54
- # run password complexity validations
55
- #user_validator = UsersValidator.new(user).password_complexity(sign_up_params[:password])
56
-
57
- # return if there are errors with the complexity validations
58
- # unless user_validator.valid?
59
- # return respond_with_error("password_complexity_error", password_complexity.failures)
60
- # end
61
-
62
55
  # persist new user
63
56
  if user.save
57
+ user.log(engine: LesliShield, source: self.class.name, action: action_name, operation: 'user_creation', description: 'User creation successfully')
64
58
  success("Account created, check your email")
65
59
  else
66
60
  raise(user.errors.full_messages.to_sentence)
@@ -30,85 +30,92 @@ Building a better future, one line of code at a time.
30
30
  // ·
31
31
  =end
32
32
 
33
- #require "uri"
34
-
35
33
  class Users::SessionsController < Devise::SessionsController
36
34
 
37
- # Creates a new session for the user and allows them access to the platform
35
+ # Creates a new session for the user and allows them access to the platform.
36
+ #
37
+ # Devise provides extension points such as Warden hooks and a custom FailureApp
38
+ # to modify the authentication flow. However, in this case we need full and
39
+ # explicit control over each step of the login process, including validation,
40
+ # logging, session creation, and redirection.
41
+ #
42
+ # For that reason, the default Devise session logic is intentionally overridden
43
+ # and reimplemented here. While this approach is less conventional and may
44
+ # introduce compatibility risks with future Devise releases, it provides a
45
+ # predictable and fully controlled authentication pipeline, which is important
46
+ # for the needs of this framework.
47
+ #
48
+ # This trade-off is accepted as part of the framework’s lifecycle: any
49
+ # incompatibilities introduced by Devise updates will be addressed and
50
+ # maintained as needed.
38
51
  def create
39
52
 
40
- # search for a existing user
41
- user = ::Lesli::User.find_for_database_authentication(email: sign_in_params[:email])
53
+ # Use guarden to check if the users credetials are valid
54
+ self.resource = warden.authenticate(auth_options)
42
55
 
43
- # respond with a no valid credentials generic error if not valid user found
44
- unless user
45
- danger(I18n.t("lesli.users/sessions.message_invalid_credentials"))
46
- redirect_to user_session_path(:r => sign_in_params[:redirect]) and return
56
+ # respond with a no valid credentials generic error if warden
57
+ # cannot validate the user
58
+ unless resource
59
+ danger(I18n.t("lesli_shield.devise/sessions.message_not_valid_credentials"))
60
+ redirect_to user_session_path(r: sign_in_params[:redirect]) and return
47
61
  end
48
62
 
49
- # save a invalid credentials log for the requested user
50
- activity = user.activities.new({ title: "session_create", description:"atempt" })
63
+ user = resource
51
64
 
52
- # check password validation
53
- unless user.valid_password?(sign_in_params[:password])
65
+ # check if user has a valid account
66
+ unless user.account
67
+ danger(I18n.t("lesli_shield.devise/sessions.message_not_confirmed_account"))
68
+ redirect_to user_session_path(r: sign_in_params[:redirect]) and return
69
+ end
54
70
 
55
- # save a invalid credentials log for the requested user
56
- activity.update(description: "invalid_credentials")
71
+ log = nil
57
72
 
58
- # respond with a no valid credentials generic error if not valid user found
59
- danger(I18n.t("lesli.users/sessions.message_invalid_credentials"))
60
- redirect_to user_session_path(:r => sign_in_params[:redirect]) and return
61
- end
73
+ # Save a log for the current login attempt
74
+ log = user.log(
75
+ engine: LesliShield,
76
+ source: self.class.name,
77
+ action: action_name,
78
+ operation: 'session_new',
79
+ description: 'Session creation attempt'
80
+ ) if defined?(LesliAudit)
62
81
 
63
82
  # check if user meet requirements to create a new session
64
- Lesli::UsersValidator.new(user).valid? do |valid, failures|
83
+ LesliShield::UserValidatorService.new(user).valid? do |valid, failures|
65
84
 
66
85
  # if user do not meet requirements to login
67
86
  unless valid
68
87
 
69
- activity.update(description: failures.join(", "))
70
-
71
- danger(failures.join(", "))
72
- redirect_to user_session_path(:r => sign_in_params[:redirect]) and return
88
+ failures_string = failures.join(", ")
89
+ danger(failures_string)
90
+ log.update(description: failures_string) if log
91
+ redirect_to user_session_path(r: sign_in_params[:redirect]) and return
73
92
  end
74
93
  end
75
94
 
76
-
77
- # remember the user (not enabled by default)
78
- # remember_me(user) if sign_in_params[:remember_me] == '1'
79
-
80
-
81
95
  # create a new session for the user
82
- current_session = Lesli::User::SessionService.new(user)
83
- .create(get_user_agent(false), request.remote_ip)
96
+ current_session = LesliShield::UserSessionService.new(user)
97
+ .create(request.remote_ip, (get_user_agent(false) if log))
84
98
  .result
85
99
 
86
100
  # make session id globally available
87
101
  session[:user_session_id] = current_session[:id]
88
102
 
89
- # create a new multi factor authentication service instance for the current user
90
- #mfa_service = User::MfaService.new(user, log)
91
-
92
- # generate a new mfa for the current session (if enabled)
93
- #mfa_service.generate do |success|
94
- # mfa was successfully generated, return the user to the mfa page
95
- # return respond_with_successful({ default_path: "mfa" }) if success
96
- #end
97
-
98
103
  # do a user login
99
- sign_in(:user, user)
104
+ sign_in(resource_name, user)
100
105
 
101
- # create a log for login atempts
102
- activity.update({
103
- title: "session_create",
104
- description: "successful",
105
- session_id: current_session[:id]
106
- })
106
+ # update logs with a successful login
107
+ log.update(
108
+ description: "Session creation successful",
109
+ session_id: current_session[:id]
110
+ ) if log
107
111
 
108
112
  # respond successful and send the path user should go
109
- #respond_with_successful({ default_path: user.has_role_with_default_path?() })
110
- #respond_with_successful({ default_path: Lesli.config.path_after_login || "/" })
111
- redirect_to(safe_redirect_path(sign_in_params[:redirect]))
113
+ # respond_with_successful({ default_path: user.has_role_with_default_path?() })
114
+ # respond_with_successful({ default_path: Lesli.config.path_after_login || "/" })
115
+ redirect_to safe_redirect_path(sign_in_params[:redirect])
116
+
117
+ # Save the user_agent for every new session
118
+ log_devices if log
112
119
  end
113
120
 
114
121
  private
@@ -0,0 +1,4 @@
1
+ module LesliShield
2
+ module InvitesHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module LesliShield
2
+ module User::RolesHelper
3
+ end
4
+ end
@@ -27,12 +27,18 @@ module LesliShield
27
27
 
28
28
  # privilege for object not found
29
29
  if granted.blank?
30
- current_user.activities.create({ title: "privilege_not_found", description: request.path })
30
+ log(
31
+ :operation => :authorize_request,
32
+ :description => "Privilege not found for: #{request.path}"
33
+ )
31
34
  return respond_with_unauthorized({ controller: params[:controller], action: params[:action] })
32
35
  end
33
36
 
34
37
  unless granted
35
- current_user.activities.create({ title: "privilege_not_granted", description: request.path })
38
+ log(
39
+ :operation => :authorize_request,
40
+ :description => "Privilege not granted for: #{request.path}"
41
+ )
36
42
  return respond_with_unauthorized({ controller: params[:controller], action: params[:action] })
37
43
  end
38
44
  end
@@ -0,0 +1,98 @@
1
+ =begin
2
+
3
+ Lesli
4
+
5
+ Copyright (c) 2026, Lesli Technologies, S. A.
6
+
7
+ This program is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program. If not, see http://www.gnu.org/licenses/.
19
+
20
+ Lesli · Ruby on Rails SaaS Development Framework.
21
+
22
+ Made with ♥ by LesliTech
23
+ Building a better future, one line of code at a time.
24
+
25
+ @contact hello@lesli.tech
26
+ @website https://www.lesli.tech
27
+ @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
28
+
29
+ // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
30
+ // ·
31
+ =end
32
+ module LesliShield
33
+ class DeviseMailer < Lesli::ApplicationLesliMailer
34
+
35
+ # Sends an email with instructions to allow the user reset the password
36
+ def reset_password_instructions(user, token, opts = {})
37
+
38
+ # defaults for new accounts/users
39
+ email_template = "devise/reset_password_instructions"
40
+ email_subject = I18n.t("core.users/confirmations.mailer_email_verification")
41
+
42
+ # email parameters
43
+ params = {
44
+ url: build_url("/password/edit", { reset_password_token: token}),
45
+ full_name: user.full_name
46
+ }
47
+
48
+ # send email
49
+ email(
50
+ params,
51
+ to: user.email,
52
+ subject: email_subject,
53
+ template_name: email_template
54
+ )
55
+ end
56
+
57
+ # Sends an email to allow the user confirm the email address
58
+ def confirmation_instructions(user, token, opts = {})
59
+
60
+ # defaults for new accounts/users
61
+ email_template = "devise/confirmation_instructions"
62
+ email_subject = I18n.t("core.users/confirmations.mailer_email_verification")
63
+
64
+ # # custom email and subject if user is changin his email address
65
+ # if !record.unconfirmed_email.blank?
66
+ # email_template = "update_email_confirmation_instructions"
67
+ # email_subject = I18n.t("core.users/confirmations.mailer_confirmation_instructions_subject")
68
+ # end
69
+
70
+ # Depending on wheter there is a new user or they are changing their email,
71
+ # one or another field will be used
72
+ email_recipient = user.unconfirmed_email || user.email
73
+
74
+ # email parameters
75
+ params = {
76
+ url: build_url("/confirmation", { confirmation_token: token})
77
+ }
78
+
79
+ # send email
80
+ email(
81
+ params,
82
+ to: email_recipient,
83
+ subject: email_subject,
84
+ template_name: email_template
85
+ )
86
+ end
87
+
88
+ def welcome(user)
89
+ email_recipient = user.unconfirmed_email || user.email
90
+ email(
91
+ { :user => user },
92
+ to: email_recipient,
93
+ subject: "test",
94
+ template_name: "devise/welcome"
95
+ )
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,23 @@
1
+ <!doctype html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office"><head><title></title><!--[if !mso]><!--><meta http-equiv="X-UA-Compatible" content="IE=edge"><!--<![endif]--><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style type="text/css">#outlook a { padding:0; }
2
+ body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; }
3
+ table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; }
4
+ img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; }
5
+ p { display:block;margin:13px 0; }</style><!--[if mso]>
6
+ <noscript>
7
+ <xml>
8
+ <o:OfficeDocumentSettings>
9
+ <o:AllowPNG/>
10
+ <o:PixelsPerInch>96</o:PixelsPerInch>
11
+ </o:OfficeDocumentSettings>
12
+ </xml>
13
+ </noscript>
14
+ <![endif]--><!--[if lte mso 11]>
15
+ <style type="text/css">
16
+ .mj-outlook-group-fix { width:100% !important; }
17
+ </style>
18
+ <![endif]--><!--[if !mso]><!--><link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css"><style type="text/css">@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);</style><!--<![endif]--><style type="text/css">@media only screen and (min-width:480px) {
19
+ .mj-column-per-100 { width:100% !important; max-width: 100%; }
20
+ }</style><style media="screen and (min-width:480px)">.moz-text-html .mj-column-per-100 { width:100% !important; max-width: 100%; }</style><style type="text/css">@media only screen and (max-width:479px) {
21
+ table.mj-full-width-mobile { width: 100% !important; }
22
+ td.mj-full-width-mobile { width: auto !important; }
23
+ }</style><style type="text/css"></style></head><body style="word-spacing:normal;background-color:#ebecf0;"><div style="background-color:#ebecf0;"><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--><div style="margin:0px auto;max-width:600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"><tbody><tr><td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;"><!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--><div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%"><tbody><tr><td align="center" style="font-size:0px;padding:0px;padding-top:60px;padding-bottom:0px;padding-left:5px;word-break:break-word;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;"><tbody><tr><td style="width:125px;"><img src="https://cdn.lesli.tech/leslicloud/brand/app-logo.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="125" height="auto"></td></tr></tbody></table></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--><div style="height:5px;line-height:5px;">&#8202;</div><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:center;color:#4a5056;"><h1>Welcome to The Lesli Family!</h1></div><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--><div style="background:#ffffff;background-color:#ffffff;margin:0px auto;border-radius:15px;max-width:600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;border-radius:15px;"><tbody><tr><td style="direction:ltr;font-size:0px;padding:40px;text-align:center;"><!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:520px;" ><![endif]--><div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%"><tbody><tr><td align="center" style="font-size:0px;padding:0px;word-break:break-word;"><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:center;color:#4a5056;"><h2><raw><% name = @data.dig(:user, :full_name) %></raw><raw><%= !name.blank? ? "Hi #{name.strip}." : nil %></raw></h2></div></td></tr><tr><td style="background:#95a3ab;font-size:0px;word-break:break-word;"><div style="height:2px;line-height:2px;">&#8202;</div></td></tr><tr><td style="font-size:0px;word-break:break-word;"><div style="height:5px;line-height:5px;">&#8202;</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;"><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:18px;line-height:1.4;text-align:center;color:#4a5056;"><p>You have been invitated to Lesli, please confirm your email address by clicking the button below:</p></div></td></tr><tr><td align="center" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;"><tbody><tr><td align="center" bgcolor="#eef6fc" role="presentation" style="border:1px solid #209cee;border-radius:5px;cursor:auto;mso-padding-alt:10px;background:#eef6fc;" valign="middle"><a href="<%= url_for(@app[:host] + @data[:url]) %>" style="display:inline-block;background:#eef6fc;color:#209cee;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:17px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px;mso-padding-alt:0px;border-radius:5px;" target="_blank">Confirm my account</a></td></tr></tbody></table></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--><div style="height:30px;line-height:30px;">&#8202;</div><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--><div style="margin:0px auto;max-width:600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"><tbody><tr><td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;"><!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--><div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%"><tbody><tr><td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;"><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:18px;font-weight:600;line-height:1;text-align:center;color:#444444;">¡Siguenos!</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;"><table cellpadding="0" cellspacing="0" width="300" border="0" style="color:#000000;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:22px;table-layout:auto;width:300px;border:none;"><tr><td align="center"><img width="45px" alt="Facebook" src="https://cdn.lesli.tech/leslicloud/emails/social-facebook.png"></td><td align="center"><img width="45px" alt="Twitter" src="https://cdn.lesli.tech/leslicloud/emails/social-twitter.png"></td><td align="center"><img width="45px" alt="Instagram" src="https://cdn.lesli.tech/leslicloud/emails/social-instagram.png"></td><td align="center"><img width="45px" alt="Linkedin" src="https://cdn.lesli.tech/leslicloud/emails/social-linkedin.png"></td></tr></table></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--><div style="margin:0px auto;max-width:600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"><tbody><tr><td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;"><!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--><div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%"><tbody><tr><td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;"><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:18px;font-weight:600;line-height:1;text-align:center;color:#444444;">Download our app</div></td></tr><tr><td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;"><table cellpadding="0" cellspacing="0" width="100%" border="0" style="color:#000000;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:22px;table-layout:auto;width:100%;border:none;"><tr><td style="text-align: right; padding: 5px;"><a target="blank" href="https://apps.apple.com/us/app/lesli/id1595893730"><img width="130px" alt="Appstore badge" src="https://cdn.lesli.tech/leslicloud/emails/appstore.png"></a></td><td style="text-align: left; padding: 5px;"><a target="blank" href="https://apps.apple.com/us/app/lesli/id1595893730"><img width="130px" alt="Playstore badge" src="https://cdn.lesli.tech/leslicloud/emails/playstore.png"></a></td></tr></table></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--><div style="height:10px;line-height:10px;">&#8202;</div><!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]--><div style="margin:0px auto;max-width:600px;"><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"><tbody><tr><td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;"><!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]--><div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;"><table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%"><tbody><tr><td align="center" style="font-size:0px;padding:4px;word-break:break-word;"><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:center;color:#666666;">Copyright &copy; <%= Time.new.year %> LesliTech</div></td></tr><tr><td align="center" style="font-size:0px;padding:4px;word-break:break-word;"><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:center;color:#666666;">Ciudad de Guatemala, Guatemala.</div></td></tr><tr><td align="center" style="font-size:0px;padding:4px;word-break:break-word;"><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:center;color:#666666;">All rights reserved.</div></td></tr><tr><td align="center" style="font-size:0px;padding:4px;padding-top:20px;word-break:break-word;"><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:center;color:#666666;">The Lesli Team</div></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></td></tr></tbody></table></div><!--[if mso | IE]></td></tr></table><![endif]--></div></body></html>