rails-auth-eassy 0.1.1

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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +206 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/stylesheets/rails/auth/application.css +15 -0
  6. data/app/controllers/concerns/rails/auth/authenticatable_controller.rb +114 -0
  7. data/app/controllers/rails/auth/application_controller.rb +9 -0
  8. data/app/controllers/rails/auth/confirmations_controller.rb +18 -0
  9. data/app/controllers/rails/auth/impersonations_controller.rb +46 -0
  10. data/app/controllers/rails/auth/mfa_controller.rb +26 -0
  11. data/app/controllers/rails/auth/otp_verifications_controller.rb +25 -0
  12. data/app/controllers/rails/auth/password_resets_controller.rb +51 -0
  13. data/app/controllers/rails/auth/profiles_controller.rb +24 -0
  14. data/app/controllers/rails/auth/registrations_controller.rb +27 -0
  15. data/app/controllers/rails/auth/security_controller.rb +27 -0
  16. data/app/controllers/rails/auth/sessions_controller.rb +69 -0
  17. data/app/controllers/rails/auth/unlocks_controller.rb +17 -0
  18. data/app/helpers/rails/auth/application_helper.rb +6 -0
  19. data/app/jobs/rails/auth/application_job.rb +6 -0
  20. data/app/mailers/rails/auth/application_mailer.rb +8 -0
  21. data/app/mailers/rails/auth/user_mailer.rb +20 -0
  22. data/app/models/concerns/rails/auth/authenticatable.rb +107 -0
  23. data/app/models/concerns/rails/auth/sessionable.rb +30 -0
  24. data/app/models/rails/auth/application_record.rb +7 -0
  25. data/app/models/rails/auth/current.rb +7 -0
  26. data/app/models/rails/auth/security_event.rb +25 -0
  27. data/app/views/layouts/rails/auth/application.html.erb +29 -0
  28. data/app/views/rails/auth/mfa/show.html.erb +24 -0
  29. data/app/views/rails/auth/otp_verifications/new.html.erb +13 -0
  30. data/app/views/rails/auth/password_resets/edit.html.erb +28 -0
  31. data/app/views/rails/auth/password_resets/new.html.erb +14 -0
  32. data/app/views/rails/auth/profiles/edit.html.erb +44 -0
  33. data/app/views/rails/auth/registrations/new.html.erb +40 -0
  34. data/app/views/rails/auth/security/sessions.html.erb +92 -0
  35. data/app/views/rails/auth/sessions/new.html.erb +20 -0
  36. data/app/views/rails/auth/user_mailer/confirmation_instructions.html.erb +5 -0
  37. data/app/views/rails/auth/user_mailer/password_reset.html.erb +8 -0
  38. data/app/views/rails/auth/user_mailer/password_reset.text.erb +8 -0
  39. data/app/views/rails/auth/user_mailer/unlock_instructions.html.erb +7 -0
  40. data/config/routes.rb +20 -0
  41. data/lib/generators/rails_auth/install/install_generator.rb +21 -0
  42. data/lib/generators/rails_auth/install/templates/rails_auth.rb +7 -0
  43. data/lib/generators/rails_auth/model/model_generator.rb +27 -0
  44. data/lib/generators/rails_auth/model/templates/create_rails_auth_tables.rb +60 -0
  45. data/lib/generators/rails_auth/model/templates/session.rb +3 -0
  46. data/lib/generators/rails_auth/model/templates/user.rb +3 -0
  47. data/lib/generators/rails_auth/views/views_generator.rb +13 -0
  48. data/lib/rails/auth/engine.rb +7 -0
  49. data/lib/rails/auth/version.rb +5 -0
  50. data/lib/rails/auth.rb +49 -0
  51. data/lib/tasks/rails/auth_tasks.rake +4 -0
  52. metadata +177 -0
@@ -0,0 +1,69 @@
1
+ module Rails
2
+ module Auth
3
+ class SessionsController < ApplicationController
4
+ skip_before_action :authenticate_user!, only: [ :new, :create ]
5
+
6
+ def new
7
+ end
8
+
9
+ def create
10
+ user = Rails::Auth.user_class.find_by(email: params[:email])
11
+
12
+ if user&.access_locked?
13
+ respond_to do |format|
14
+ format.html { redirect_to new_session_path, alert: "Your account is locked. Please check your email for unlock instructions." }
15
+ format.json { render json: { error: "Account locked" }, status: :locked }
16
+ end
17
+ return
18
+ end
19
+
20
+ if user&.authenticate(params[:password])
21
+ unless user.confirmed?
22
+ respond_to do |format|
23
+ format.html { redirect_to new_session_path, alert: "Please confirm your email address before signing in." }
24
+ format.json { render json: { error: "Email not confirmed" }, status: :unauthorized }
25
+ end
26
+ return
27
+ end
28
+
29
+ user.update(failed_attempts: 0) # Reset on success
30
+
31
+ if user.otp_enabled?
32
+ session[:otp_user_id] = user.id
33
+ respond_to do |format|
34
+ format.html { redirect_to new_otp_verification_path }
35
+ format.json { render json: { mfa_required: true }, status: :accepted }
36
+ end
37
+ else
38
+ sign_in(user)
39
+ respond_to do |format|
40
+ format.html { redirect_to main_app.root_path, notice: "Signed in successfully." }
41
+ format.json { render json: { token: Rails::Auth.encode_jwt(user_id: user.id), user: user.as_json(only: [ :id, :email, :role ]) } }
42
+ end
43
+ end
44
+ else
45
+ if user
46
+ user.increment_failed_attempts!
47
+ user.log_security_event!(:login_failed, request)
48
+ message = user.access_locked? ? "Account locked. Check your email." : "Invalid email or password."
49
+ else
50
+ message = "Invalid email or password."
51
+ end
52
+
53
+ respond_to do |format|
54
+ format.html do
55
+ flash.now[:alert] = message
56
+ render :new, status: :unprocessable_entity
57
+ end
58
+ format.json { render json: { error: message }, status: :unauthorized }
59
+ end
60
+ end
61
+ end
62
+
63
+ def destroy
64
+ sign_out
65
+ redirect_to main_app.root_path, notice: "Signed out successfully."
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,17 @@
1
+ module Rails
2
+ module Auth
3
+ class UnlocksController < ApplicationController
4
+ skip_before_action :authenticate_user!
5
+
6
+ def show
7
+ user = Rails::Auth.user_class.find_by(unlock_token: params[:unlock_token])
8
+ if user
9
+ user.unlock_access!
10
+ redirect_to new_session_path, notice: "Your account has been unlocked. Please sign in."
11
+ else
12
+ redirect_to new_session_path, alert: "Invalid unlock token."
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ module Rails
2
+ module Auth
3
+ module ApplicationHelper
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Rails
2
+ module Auth
3
+ class ApplicationJob < ActiveJob::Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ module Rails
2
+ module Auth
3
+ class ApplicationMailer < ActionMailer::Base
4
+ default from: "from@example.com"
5
+ layout "mailer"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ module Rails
2
+ module Auth
3
+ class UserMailer < ApplicationMailer
4
+ def password_reset(user)
5
+ @user = user
6
+ mail to: user.email, subject: "Password Reset Instructions"
7
+ end
8
+
9
+ def confirmation_instructions(user)
10
+ @user = user
11
+ mail to: user.email, subject: "Confirmation Instructions"
12
+ end
13
+
14
+ def unlock_instructions(user)
15
+ @user = user
16
+ mail to: user.email, subject: "Unlock Instructions"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,107 @@
1
+ module Rails
2
+ module Auth
3
+ module Authenticatable
4
+ extend ActiveSupport::Concern
5
+ included do
6
+ has_secure_password
7
+ has_many :sessions, class_name: Rails::Auth.session_class_name, dependent: :destroy
8
+ has_many :security_events, class_name: "Rails::Auth::SecurityEvent", dependent: :destroy
9
+
10
+ validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
11
+
12
+ validates :password, allow_nil: true, length: { minimum: 8 }
13
+
14
+ enum :role, { user: 0, moderator: 1, admin: 2 }
15
+ has_one_attached :avatar
16
+
17
+ before_create :generate_confirmation_token, unless: :confirmed?
18
+ end
19
+
20
+ # Confirmable
21
+ def confirm!
22
+ update!(confirmed_at: Time.current, confirmation_token: nil)
23
+ end
24
+
25
+ def confirmed?
26
+ confirmed_at.present?
27
+ end
28
+
29
+ def generate_confirmation_token
30
+ self.confirmation_token = SecureRandom.hex(20)
31
+ self.confirmation_sent_at = Time.current
32
+ end
33
+
34
+ def send_confirmation_instructions
35
+ generate_confirmation_token
36
+ save!
37
+ Rails::Auth::UserMailer.confirmation_instructions(self).deliver_now
38
+ end
39
+
40
+ # Lockable
41
+ def lock_access!
42
+ update!(locked_at: Time.current, unlock_token: SecureRandom.hex(20))
43
+ Rails::Auth::UserMailer.unlock_instructions(self).deliver_now
44
+ end
45
+
46
+ def unlock_access!
47
+ update!(locked_at: nil, failed_attempts: 0, unlock_token: nil)
48
+ end
49
+
50
+ def access_locked?
51
+ locked_at.present? && locked_at > 1.hour.ago
52
+ end
53
+
54
+ def increment_failed_attempts!
55
+ self.failed_attempts += 1
56
+ if failed_attempts >= 5
57
+ lock_access!
58
+ else
59
+ save!
60
+ end
61
+ end
62
+
63
+ # MFA
64
+ def generate_otp_secret!
65
+ update!(otp_secret: ::ROTP::Base32.random)
66
+ end
67
+
68
+ def verify_otp(code)
69
+ return false unless otp_secret.present?
70
+ totp = ::ROTP::TOTP.new(otp_secret, issuer: "RailsAuth")
71
+ totp.verify(code, drift_behind: 15)
72
+ end
73
+
74
+ def otp_provisioning_uri
75
+ totp = ::ROTP::TOTP.new(otp_secret, issuer: "RailsAuth")
76
+ totp.provisioning_uri(email)
77
+ end
78
+
79
+ def log_security_event!(event_type, request = nil, details = {})
80
+ security_events.create!(
81
+ event_type: event_type,
82
+ ip_address: request&.remote_ip,
83
+ user_agent: request&.user_agent,
84
+ details: details
85
+ )
86
+ end
87
+
88
+ # Password Reset
89
+
90
+
91
+ def generate_password_reset_token!
92
+ update!(
93
+ reset_token: SecureRandom.hex(20),
94
+ reset_sent_at: Time.current
95
+ )
96
+ end
97
+
98
+ def password_reset_token_valid?
99
+ reset_sent_at.present? && reset_sent_at > 2.hours.ago
100
+ end
101
+
102
+ def clear_password_reset_token!
103
+ update!(reset_token: nil, reset_sent_at: nil)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,30 @@
1
+ module Rails
2
+ module Auth
3
+ module Sessionable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ belongs_to :user, class_name: Rails::Auth.user_class_name
8
+
9
+ before_create :generate_token
10
+ before_create :set_device_info
11
+
12
+ scope :active, -> { where("last_active_at > ?", 1.month.ago) }
13
+ end
14
+
15
+ private
16
+
17
+ def generate_token
18
+ self.token = SecureRandom.hex(32)
19
+ end
20
+
21
+ def set_device_info
22
+ if user_agent.present?
23
+ ua = UserAgent.parse(user_agent)
24
+ self.browser = "#{ua.browser} #{ua.version}"
25
+ self.os = ua.platform
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,7 @@
1
+ module Rails
2
+ module Auth
3
+ class ApplicationRecord < ActiveRecord::Base
4
+ self.abstract_class = true
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Rails
2
+ module Auth
3
+ class Current < ActiveSupport::CurrentAttributes
4
+ attribute :user, :session
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ module Rails
2
+ module Auth
3
+ class SecurityEvent < ApplicationRecord
4
+ self.table_name = "security_events"
5
+
6
+ belongs_to :user, class_name: Rails::Auth.user_class_name
7
+
8
+ validates :event_type, presence: true
9
+
10
+ enum :event_type, {
11
+ login_success: "login_success",
12
+ login_failed: "login_failed",
13
+ logout: "logout",
14
+ password_changed: "password_changed",
15
+ password_reset_requested: "password_reset_requested",
16
+ mfa_enabled: "mfa_enabled",
17
+ mfa_disabled: "mfa_disabled",
18
+ account_locked: "account_locked",
19
+ account_unlocked: "account_unlocked",
20
+ impersonation_started: "impersonation_started",
21
+ impersonation_stopped: "impersonation_stopped"
22
+ }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Rails auth</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= yield :head %>
9
+
10
+ <%= stylesheet_link_tag "rails/auth/application", media: "all" %>
11
+ </head>
12
+ <body>
13
+ <% if impersonating? %>
14
+ <div style="background: #ffcc00; padding: 10px; text-align: center;">
15
+ You are currently impersonating <strong><%= current_user.email %></strong>.
16
+ <%= button_to "Stop Impersonation", rails_auth.stop_impersonations_path, method: :delete, style: "margin-left: 10px;" %>
17
+ </div>
18
+ <% end %>
19
+
20
+ <% flash.each do |type, message| %>
21
+ <div class="flash flash-<%= type %>">
22
+ <%= message %>
23
+ </div>
24
+ <% end %>
25
+
26
+ <%= yield %>
27
+
28
+ </body>
29
+ </html>
@@ -0,0 +1,24 @@
1
+ <h2>Two-Factor Authentication (MFA)</h2>
2
+
3
+ <% if current_user.otp_enabled? %>
4
+ <p>Two-factor authentication is currently enabled.</p>
5
+ <%= button_to "Disable MFA", rails_auth.mfa_path, method: :delete, data: { confirm: "Are you sure?" } %>
6
+ <% else %>
7
+ <p>To enable two-factor authentication, scan the QR code below with your authenticator app (Google Authenticator, Authy, etc.):</p>
8
+
9
+ <div>
10
+ <%= @qrcode.as_svg(module_size: 4).html_safe %>
11
+ </div>
12
+
13
+ <p>After scanning, enter the 6-digit code from your app to verify:</p>
14
+
15
+ <%= form_with url: rails_auth.mfa_path do |f| %>
16
+ <div>
17
+ <%= f.label :otp_code, "Verification Code" %>
18
+ <%= f.text_field :otp_code, autocomplete: "one-time-code" %>
19
+ </div>
20
+ <%= f.submit "Verify and Enable MFA" %>
21
+ <% end %>
22
+ <% end %>
23
+
24
+ <%= link_to "Back to Security", rails_auth.security_sessions_path %>
@@ -0,0 +1,13 @@
1
+ <h2>Two-Factor Authentication</h2>
2
+
3
+ <p>Enter the 6-digit code from your authenticator app to complete sign in.</p>
4
+
5
+ <%= form_with url: rails_auth.otp_verification_path do |f| %>
6
+ <div>
7
+ <%= f.label :otp_code, "Verification Code" %>
8
+ <%= f.text_field :otp_code, autofocus: true, autocomplete: "one-time-code" %>
9
+ </div>
10
+ <%= f.submit "Verify and Sign In" %>
11
+ <% end %>
12
+
13
+ <%= link_to "Cancel", rails_auth.new_session_path %>
@@ -0,0 +1,28 @@
1
+ <h2>Reset Password</h2>
2
+
3
+ <%= form_with model: @user, url: rails_auth.password_reset_path(@user.reset_token), method: :patch do |f| %>
4
+ <% if @user.errors.any? %>
5
+ <div id="error_explanation">
6
+ <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
7
+ <ul>
8
+ <% @user.errors.full_messages.each do |message| %>
9
+ <li><%= message %></li>
10
+ <% end %>
11
+ </ul>
12
+ </div>
13
+ <% end %>
14
+
15
+ <div>
16
+ <%= f.label :password %>
17
+ <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
18
+ </div>
19
+
20
+ <div>
21
+ <%= f.label :password_confirmation %>
22
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
23
+ </div>
24
+
25
+ <div>
26
+ <%= f.submit "Update Password" %>
27
+ </div>
28
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <h2>Forgot Password</h2>
2
+
3
+ <%= form_with url: rails_auth.password_resets_path do |f| %>
4
+ <div>
5
+ <%= f.label :email %>
6
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
7
+ </div>
8
+
9
+ <div>
10
+ <%= f.submit "Send reset instructions" %>
11
+ </div>
12
+ <% end %>
13
+
14
+ <%= link_to "Back to Sign in", rails_auth.new_session_path %>
@@ -0,0 +1,44 @@
1
+ <h2>Edit Profile</h2>
2
+
3
+ <%= form_with model: @user, url: rails_auth.profile_path, method: :patch do |f| %>
4
+ <% if @user.errors.any? %>
5
+ <div id="error_explanation">
6
+ <h2><%= pluralize(@user.errors.count, "error") %> prohibited this profile from being saved:</h2>
7
+ <ul>
8
+ <% @user.errors.full_messages.each do |message| %>
9
+ <li><%= message %></li>
10
+ <% end %>
11
+ </ul>
12
+ </div>
13
+ <% end %>
14
+
15
+ <div>
16
+ <% if @user.avatar.attached? %>
17
+ <%= image_tag @user.avatar.variant(resize_to_limit: [100, 100]) %>
18
+ <% end %>
19
+ <%= f.label :avatar %>
20
+ <%= f.file_field :avatar %>
21
+ </div>
22
+
23
+ <div>
24
+ <%= f.label :email %>
25
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
26
+ </div>
27
+
28
+ <div>
29
+ <%= f.label :password %>
30
+ <i>(leave blank if you don't want to change it)</i>
31
+ <%= f.password_field :password, autocomplete: "new-password" %>
32
+ </div>
33
+
34
+ <div>
35
+ <%= f.label :password_confirmation %>
36
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
37
+ </div>
38
+
39
+ <div>
40
+ <%= f.submit "Update Profile" %>
41
+ </div>
42
+ <% end %>
43
+
44
+ <%= link_to "Back to Security", rails_auth.security_sessions_path %>
@@ -0,0 +1,40 @@
1
+ <h2>Sign up</h2>
2
+
3
+ <%= form_with model: @user, url: rails_auth.registration_path do |f| %>
4
+ <% if @user.errors.any? %>
5
+ <div id="error_explanation">
6
+ <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
7
+ <ul>
8
+ <% @user.errors.full_messages.each do |message| %>
9
+ <li><%= message %></li>
10
+ <% end %>
11
+ </ul>
12
+ </div>
13
+ <% end %>
14
+
15
+ <div>
16
+ <%= f.label :email %>
17
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
18
+ </div>
19
+
20
+ <div>
21
+ <%= f.label :avatar %>
22
+ <%= f.file_field :avatar %>
23
+ </div>
24
+
25
+ <div>
26
+ <%= f.label :password %>
27
+ <%= f.password_field :password, autocomplete: "new-password" %>
28
+ </div>
29
+
30
+ <div>
31
+ <%= f.label :password_confirmation %>
32
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
33
+ </div>
34
+
35
+ <div>
36
+ <%= f.submit "Sign up" %>
37
+ </div>
38
+ <% end %>
39
+
40
+ <%= link_to "Sign in", rails_auth.new_session_path %>
@@ -0,0 +1,92 @@
1
+ <h2>Account Security</h2>
2
+
3
+ <div style="margin-bottom: 20px;">
4
+ <% if current_user.avatar.attached? %>
5
+ <%= image_tag current_user.avatar.variant(resize_to_limit: [100, 100]), style: "border-radius: 50%;" %>
6
+ <% else %>
7
+ <div style="width: 100px; height: 100px; background: #eee; border-radius: 50%; display: flex; align-items: center; justify-content: center;">
8
+ No Avatar
9
+ </div>
10
+ <% end %>
11
+ <p><strong><%= current_user.email %></strong> (<%= current_user.role.capitalize %>)</p>
12
+ <%= link_to "Edit Profile & Avatar", rails_auth.edit_profile_path %>
13
+ </div>
14
+
15
+ <hr>
16
+
17
+ <h2>Security Audit Log</h2>
18
+
19
+ <table>
20
+ <thead>
21
+ <tr>
22
+ <th>Event</th>
23
+ <th>IP Address</th>
24
+ <th>Date</th>
25
+ <th>Details</th>
26
+ </tr>
27
+ </thead>
28
+ <tbody>
29
+ <% current_user.security_events.order(created_at: :desc).limit(20).each do |event| %>
30
+ <tr>
31
+ <td><%= event.event_type.titleize %></td>
32
+ <td><%= event.ip_address %></td>
33
+ <td><%= event.created_at.strftime("%Y-%m-%d %H:%M:%S") %></td>
34
+ <td><small><%= event.details %></small></td>
35
+ </tr>
36
+ <% end %>
37
+ </tbody>
38
+ </table>
39
+
40
+ <hr>
41
+
42
+ <h2>Active Sessions</h2>
43
+
44
+ <%= button_to "Sign out of all devices", rails_auth.revoke_all_sessions_path, method: :delete, data: { confirm: "Are you sure? This will sign you out of all devices including this one." } %>
45
+
46
+ <hr>
47
+
48
+ <div>
49
+ <strong>Two-Factor Authentication (MFA)</strong>
50
+ <% if current_user.otp_enabled? %>
51
+ <span style="color: green;">Enabled</span>
52
+ <% else %>
53
+ <span style="color: red;">Disabled</span>
54
+ <% end %>
55
+ <%= link_to "Manage MFA", rails_auth.mfa_path %>
56
+ </div>
57
+
58
+ <hr>
59
+
60
+ <table>
61
+ <thead>
62
+ <tr>
63
+ <th>Device / Browser</th>
64
+ <th>IP Address</th>
65
+ <th>Last Active</th>
66
+ <th>Status</th>
67
+ <th>Action</th>
68
+ </tr>
69
+ </thead>
70
+ <tbody>
71
+ <% @sessions.each do |session| %>
72
+ <tr>
73
+ <td>
74
+ <strong><%= session.browser || "Unknown Browser" %></strong><br>
75
+ <small><%= session.os || "Unknown OS" %></small>
76
+ </td>
77
+ <td><%= session.ip_address %></td>
78
+ <td><%= time_ago_in_words(session.last_active_at) %> ago</td>
79
+ <td>
80
+ <% if session == current_session %>
81
+ <strong>Current</strong>
82
+ <% else %>
83
+ Active
84
+ <% end %>
85
+ </td>
86
+ <td>
87
+ <%= button_to "Revoke", rails_auth.revoke_session_path(session), method: :delete, data: { confirm: "Are you sure?" } %>
88
+ </td>
89
+ </tr>
90
+ <% end %>
91
+ </tbody>
92
+ </table>
@@ -0,0 +1,20 @@
1
+ <h2>Sign in</h2>
2
+
3
+ <%= form_with url: rails_auth.session_path do |f| %>
4
+ <div>
5
+ <%= f.label :email %>
6
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
7
+ </div>
8
+
9
+ <div>
10
+ <%= f.label :password %>
11
+ <%= f.password_field :password, autocomplete: "current-password" %>
12
+ </div>
13
+
14
+ <div>
15
+ <%= f.submit "Sign in" %>
16
+ <%= link_to "Forgot password?", rails_auth.new_password_reset_path %>
17
+ </div>
18
+ <% end %>
19
+
20
+ <%= link_to "Sign up", rails_auth.new_registration_path %>
@@ -0,0 +1,5 @@
1
+ <h1>Welcome <%= @user.email %>!</h1>
2
+
3
+ <p>You can confirm your account email through the link below:</p>
4
+
5
+ <p><%= link_to 'Confirm my account', rails_auth.confirmation_url(confirmation_token: @user.confirmation_token) %></p>
@@ -0,0 +1,8 @@
1
+ <h1>Password Reset</h1>
2
+
3
+ <p>To reset your password, click the link below:</p>
4
+
5
+ <p><%= link_to "Reset Password", rails_auth.edit_password_reset_url(@user.reset_token) %></p>
6
+
7
+ <p>If you did not request a password reset, please ignore this email.</p>
8
+ <p>This link will expire in 2 hours.</p>
@@ -0,0 +1,8 @@
1
+ Password Reset
2
+
3
+ To reset your password, visit the following URL:
4
+
5
+ <%= rails_auth.edit_password_reset_url(@user.reset_token) %>
6
+
7
+ If you did not request a password reset, please ignore this email.
8
+ This link will expire in 2 hours.
@@ -0,0 +1,7 @@
1
+ <h1>Hello <%= @user.email %>!</h1>
2
+
3
+ <p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>
4
+
5
+ <p>Click the link below to unlock your account:</p>
6
+
7
+ <p><%= link_to 'Unlock my account', rails_auth.unlock_url(unlock_token: @user.unlock_token) %></p>