rails_authentication 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +108 -0
  3. data/lib/generators/authentication/authentication_generator.rb +126 -0
  4. data/lib/generators/authentication/features/confirmable.rb +25 -0
  5. data/lib/generators/authentication/features/invitable.rb +26 -0
  6. data/lib/generators/authentication/features/lockable.rb +25 -0
  7. data/lib/generators/authentication/features/recoverable.rb +25 -0
  8. data/lib/generators/authentication/features/registerable.rb +17 -0
  9. data/lib/generators/authentication/features/rememberable.rb +16 -0
  10. data/lib/generators/authentication/features/timeoutable.rb +17 -0
  11. data/lib/generators/authentication/features/trackable.rb +17 -0
  12. data/lib/generators/authentication/features/validatable.rb +15 -0
  13. data/lib/generators/authentication/templates/app/controllers/concerns/authentication.rb.tt +96 -0
  14. data/lib/generators/authentication/templates/app/controllers/confirmations_controller.rb.tt +27 -0
  15. data/lib/generators/authentication/templates/app/controllers/invitations_controller.rb.tt +36 -0
  16. data/lib/generators/authentication/templates/app/controllers/passwords_controller.rb.tt +39 -0
  17. data/lib/generators/authentication/templates/app/controllers/registrations_controller.rb.tt +56 -0
  18. data/lib/generators/authentication/templates/app/controllers/sessions_controller.rb.tt +64 -0
  19. data/lib/generators/authentication/templates/app/controllers/unlocks_controller.rb.tt +23 -0
  20. data/lib/generators/authentication/templates/app/mailers/confirmations_mailer.rb.tt +10 -0
  21. data/lib/generators/authentication/templates/app/mailers/invitations_mailer.rb.tt +6 -0
  22. data/lib/generators/authentication/templates/app/mailers/passwords_mailer.rb.tt +6 -0
  23. data/lib/generators/authentication/templates/app/mailers/unlocks_mailer.rb.tt +6 -0
  24. data/lib/generators/authentication/templates/app/models/concerns/confirmable_concern.rb.tt +74 -0
  25. data/lib/generators/authentication/templates/app/models/concerns/invitable_concern.rb.tt +86 -0
  26. data/lib/generators/authentication/templates/app/models/concerns/lockable_concern.rb.tt +48 -0
  27. data/lib/generators/authentication/templates/app/models/concerns/recoverable_concern.rb.tt +33 -0
  28. data/lib/generators/authentication/templates/app/models/concerns/rememberable_concern.rb.tt +15 -0
  29. data/lib/generators/authentication/templates/app/models/concerns/timeoutable_concern.rb.tt +9 -0
  30. data/lib/generators/authentication/templates/app/models/concerns/trackable_concern.rb.tt +7 -0
  31. data/lib/generators/authentication/templates/app/models/concerns/validatable_concern.rb.tt +40 -0
  32. data/lib/generators/authentication/templates/app/models/user_auth.rb.tt +18 -0
  33. data/lib/generators/authentication/templates/app/views/confirmations/new.html.erb.tt +8 -0
  34. data/lib/generators/authentication/templates/app/views/confirmations_mailer/confirmation_instructions.html.erb.tt +6 -0
  35. data/lib/generators/authentication/templates/app/views/confirmations_mailer/confirmation_instructions.text.erb.tt +4 -0
  36. data/lib/generators/authentication/templates/app/views/invitations/edit.html.erb.tt +9 -0
  37. data/lib/generators/authentication/templates/app/views/invitations/new.html.erb.tt +9 -0
  38. data/lib/generators/authentication/templates/app/views/invitations_mailer/invitation_instructions.html.erb.tt +6 -0
  39. data/lib/generators/authentication/templates/app/views/invitations_mailer/invitation_instructions.text.erb.tt +4 -0
  40. data/lib/generators/authentication/templates/app/views/passwords_mailer/reset.html.erb.tt +6 -0
  41. data/lib/generators/authentication/templates/app/views/passwords_mailer/reset.text.erb.tt +4 -0
  42. data/lib/generators/authentication/templates/app/views/registrations/edit.html.erb.tt +22 -0
  43. data/lib/generators/authentication/templates/app/views/registrations/new.html.erb.tt +21 -0
  44. data/lib/generators/authentication/templates/app/views/sessions/new.html.erb.tt +23 -0
  45. data/lib/generators/authentication/templates/app/views/unlocks/new.html.erb.tt +8 -0
  46. data/lib/generators/authentication/templates/app/views/unlocks_mailer/unlock_instructions.html.erb.tt +6 -0
  47. data/lib/generators/authentication/templates/app/views/unlocks_mailer/unlock_instructions.text.erb.tt +6 -0
  48. data/lib/generators/authentication/templates/db/migrate/add_confirmable_to_users.rb.tt +12 -0
  49. data/lib/generators/authentication/templates/db/migrate/add_invitable_to_users.rb.tt +12 -0
  50. data/lib/generators/authentication/templates/db/migrate/add_lockable_to_users.rb.tt +9 -0
  51. data/lib/generators/authentication/templates/db/migrate/add_recoverable_to_users.rb.tt +8 -0
  52. data/lib/generators/authentication/templates/db/migrate/add_rememberable_to_users.rb.tt +5 -0
  53. data/lib/generators/authentication/templates/db/migrate/create_user_auths.rb.tt +14 -0
  54. data/lib/rails_authentication/version.rb +5 -0
  55. data/lib/rails_authentication.rb +6 -0
  56. metadata +112 -0
@@ -0,0 +1,56 @@
1
+ class RegistrationsController < ApplicationController
2
+ allow_unauthenticated_access only: %i[ new create ]
3
+
4
+ def new
5
+ @user = User.new
6
+ end
7
+
8
+ def create
9
+ @user = User.new(user_params)
10
+
11
+ if @user.save
12
+ <% if confirmable? -%>
13
+ @user.send_confirmation_instructions
14
+ redirect_to new_session_path, notice: "Welcome! Check your email to confirm your account."
15
+ <% else -%>
16
+ start_new_session_for @user
17
+ redirect_to after_authentication_url, notice: "Welcome! You have signed up successfully."
18
+ <% end -%>
19
+ else
20
+ render :new, status: :unprocessable_content
21
+ end
22
+ end
23
+
24
+ def edit
25
+ @user = Current.user
26
+ end
27
+
28
+ def update
29
+ @user = Current.user
30
+
31
+ if @user.update(user_params_for_update)
32
+ redirect_to edit_registration_path, notice: "Your account has been updated."
33
+ else
34
+ render :edit, status: :unprocessable_content
35
+ end
36
+ end
37
+
38
+ def destroy
39
+ user = Current.user
40
+ terminate_session
41
+ user.destroy
42
+ redirect_to new_session_path, notice: "Your account has been deleted.", status: :see_other
43
+ end
44
+
45
+ private
46
+
47
+ def user_params
48
+ params.permit(:email_address, :password, :password_confirmation)
49
+ end
50
+
51
+ def user_params_for_update
52
+ permitted = user_params
53
+ permitted = permitted.except(:password, :password_confirmation) if permitted[:password].blank?
54
+ permitted
55
+ end
56
+ end
@@ -0,0 +1,64 @@
1
+ class SessionsController < ApplicationController
2
+ allow_unauthenticated_access only: %i[ new create ]
3
+ rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_path, alert: "Try again later." }
4
+
5
+ def new
6
+ end
7
+
8
+ def create
9
+ user = User.authenticate_by(params.permit(:email_address, :password))
10
+
11
+ if user.nil?
12
+ <% if lockable? || trackable? -%>
13
+ account = User.find_by(email_address: params[:email_address])
14
+ <% end -%>
15
+ <% if lockable? -%>
16
+ account&.register_failed_attempt!
17
+ <% end -%>
18
+ <% if trackable? -%>
19
+ record_authentication_attempt(account, success: false, failure_reason: "invalid_credentials")
20
+ <% end -%>
21
+ redirect_to new_session_path, alert: "Try another email address or password."
22
+ <% if lockable? -%>
23
+ elsif user.locked?
24
+ <% if trackable? -%>
25
+ record_authentication_attempt(user, success: false, failure_reason: "locked")
26
+ <% end -%>
27
+ redirect_to new_session_path, alert: "Your account is locked. Check your email for unlock instructions."
28
+ <% end -%>
29
+ <% if confirmable? -%>
30
+ elsif !user.confirmed?
31
+ <% if trackable? -%>
32
+ record_authentication_attempt(user, success: false, failure_reason: "unconfirmed")
33
+ <% end -%>
34
+ redirect_to new_session_path, alert: "You must confirm your email address before signing in."
35
+ <% end -%>
36
+ else
37
+ <% if lockable? -%>
38
+ user.reset_failed_attempts!
39
+ <% end -%>
40
+ <% if trackable? -%>
41
+ record_authentication_attempt(user, success: true)
42
+ <% end -%>
43
+ <% if rememberable? -%>
44
+ start_new_session_for user, remember: params[:remember_me] == "1"
45
+ <% else -%>
46
+ start_new_session_for user
47
+ <% end -%>
48
+ redirect_to after_authentication_url
49
+ end
50
+ end
51
+
52
+ def destroy
53
+ terminate_session
54
+ redirect_to new_session_path, status: :see_other
55
+ end
56
+ <% if trackable? -%>
57
+
58
+ private
59
+
60
+ def record_authentication_attempt(user, success:, failure_reason: nil)
61
+ UserAuth.record(user, request, success: success, failure_reason: failure_reason)
62
+ end
63
+ <% end -%>
64
+ end
@@ -0,0 +1,23 @@
1
+ class UnlocksController < ApplicationController
2
+ allow_unauthenticated_access
3
+ rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_unlock_path, alert: "Try again later." }
4
+
5
+ def new
6
+ end
7
+
8
+ def create
9
+ user = User.find_by(email_address: params[:email_address])
10
+ user.send_unlock_instructions if user&.locked?
11
+
12
+ redirect_to new_session_path, notice: "Unlock instructions sent (if user with that email address exists and is locked)."
13
+ end
14
+
15
+ def show
16
+ if user = User.find_by_valid_unlock_token(params[:token])
17
+ user.unlock!
18
+ redirect_to new_session_path, notice: "Your account has been unlocked. Please sign in."
19
+ else
20
+ redirect_to new_unlock_path, alert: "Unlock link is invalid or has expired."
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,10 @@
1
+ class ConfirmationsMailer < ApplicationMailer
2
+ def confirmation_instructions(user)
3
+ @user = user
4
+ <% if reconfirmable? -%>
5
+ mail subject: "Confirm your email address", to: user.unconfirmed_email.presence || user.email_address
6
+ <% else -%>
7
+ mail subject: "Confirm your email address", to: user.email_address
8
+ <% end -%>
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ class InvitationsMailer < ApplicationMailer
2
+ def invitation_instructions(user)
3
+ @user = user
4
+ mail subject: "You have been invited", to: user.email_address
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class PasswordsMailer < ApplicationMailer
2
+ def reset(user)
3
+ @user = user
4
+ mail subject: "Reset your password", to: user.email_address
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class UnlocksMailer < ApplicationMailer
2
+ def unlock_instructions(user)
3
+ @user = user
4
+ mail subject: "Unlock your account", to: user.email_address
5
+ end
6
+ end
@@ -0,0 +1,74 @@
1
+ module ConfirmableConcern
2
+ extend ActiveSupport::Concern
3
+
4
+ CONFIRMATION_TOKEN_EXPIRES_IN = 24.hours
5
+ <% if reconfirmable? -%>
6
+
7
+ included do
8
+ before_update :postpone_email_address_change, if: :postponing_email_address_change?
9
+ after_update_commit :deliver_reconfirmation_instructions, if: -> { saved_change_to_unconfirmed_email? && unconfirmed_email.present? }
10
+ end
11
+ <% end -%>
12
+
13
+ class_methods do
14
+ def find_by_valid_confirmation_token(token)
15
+ return if token.blank?
16
+
17
+ user = find_by(confirmation_token: token)
18
+ user if user&.confirmation_sent_at&.after?(CONFIRMATION_TOKEN_EXPIRES_IN.ago)
19
+ end
20
+ end
21
+
22
+ def confirmed?
23
+ confirmed_at.present?
24
+ end
25
+
26
+ def confirm!
27
+ <% if reconfirmable? -%>
28
+ @confirming = true
29
+ self.email_address = unconfirmed_email if unconfirmed_email.present?
30
+ self.unconfirmed_email = nil
31
+ self.confirmed_at = Time.current
32
+ self.confirmation_token = nil
33
+ self.confirmation_sent_at = nil
34
+ save!
35
+ ensure
36
+ @confirming = false
37
+ <% else -%>
38
+ update!(confirmed_at: Time.current, confirmation_token: nil, confirmation_sent_at: nil)
39
+ <% end -%>
40
+ end
41
+
42
+ def send_confirmation_instructions
43
+ generate_confirmation_token!
44
+ ConfirmationsMailer.confirmation_instructions(self).deliver_later
45
+ end
46
+
47
+ def generate_confirmation_token!
48
+ update!(confirmation_token: generate_confirmable_token, confirmation_sent_at: Time.current)
49
+ end
50
+ <% if reconfirmable? -%>
51
+
52
+ private
53
+ def postponing_email_address_change?
54
+ !@confirming && email_address_changed? && confirmed?
55
+ end
56
+
57
+ def postpone_email_address_change
58
+ self.unconfirmed_email = email_address
59
+ self.email_address = email_address_in_database
60
+ self.confirmation_token = generate_confirmable_token
61
+ self.confirmation_sent_at = Time.current
62
+ end
63
+
64
+ def deliver_reconfirmation_instructions
65
+ ConfirmationsMailer.confirmation_instructions(self).deliver_later
66
+ end
67
+ <% end -%>
68
+
69
+ protected
70
+
71
+ def generate_confirmable_token
72
+ SecureRandom.urlsafe_base64(32)
73
+ end
74
+ end
@@ -0,0 +1,86 @@
1
+ module InvitableConcern
2
+ extend ActiveSupport::Concern
3
+
4
+ INVITATION_TOKEN_EXPIRES_IN = 7.days
5
+
6
+ included do
7
+ belongs_to :invited_by, polymorphic: true, optional: true
8
+ end
9
+
10
+ class_methods do
11
+ def invite!(email_address, invited_by: nil)
12
+ user = find_by(email_address: email_address)
13
+
14
+ if user
15
+ # Re-inviting an existing pending invitee refreshes their token; anyone else
16
+ # is returned untouched (and no email is sent).
17
+ if user.invitation_pending?
18
+ user.update!(invitation_token: generate_invitable_token, invitation_created_at: Time.current)
19
+ user.send_invitation_instructions
20
+ invited_by.increment!(:invitations_count) if invited_by.respond_to?(:invitations_count)
21
+ end
22
+
23
+ return user
24
+ end
25
+
26
+ user = new(
27
+ email_address: email_address,
28
+ password: generate_temporary_password,
29
+ invited_by: invited_by,
30
+ invitation_token: generate_invitable_token,
31
+ invitation_created_at: Time.current
32
+ )
33
+
34
+ if user.save
35
+ user.send_invitation_instructions
36
+ invited_by.increment!(:invitations_count) if invited_by.respond_to?(:invitations_count)
37
+ end
38
+
39
+ user
40
+ end
41
+
42
+ def find_by_valid_invitation_token(token)
43
+ return if token.blank?
44
+
45
+ user = find_by(invitation_token: token)
46
+ user if user&.invitation_created_at&.after?(INVITATION_TOKEN_EXPIRES_IN.ago)
47
+ end
48
+
49
+ protected
50
+
51
+ def generate_invitable_token
52
+ SecureRandom.urlsafe_base64(32)
53
+ end
54
+
55
+ def generate_temporary_password
56
+ lower_letter = ("a".."z").to_a.sample(2)
57
+ upper_letter = ("A".."Z").to_a.sample(2)
58
+ digits = (1..5).to_a.sample(2)
59
+ special = %w[~ ! @ % & _ + -].sample(2)
60
+ prefix = (lower_letter + upper_letter + digits + special).join
61
+ SecureRandom.base58(16).sub(/\A.{#{prefix.length}}/, prefix)
62
+ end
63
+ end
64
+
65
+ def invitation_pending?
66
+ invitation_token.present? && invitation_accepted_at.nil?
67
+ end
68
+
69
+ def send_invitation_instructions
70
+ update!(invitation_sent_at: Time.current)
71
+ InvitationsMailer.invitation_instructions(self).deliver_later
72
+ end
73
+
74
+ def accept_invitation!(password:, password_confirmation:)
75
+ assign_attributes(
76
+ password: password,
77
+ password_confirmation: password_confirmation,
78
+ invitation_token: nil,
79
+ invitation_accepted_at: Time.current
80
+ )
81
+ <% if confirmable? -%>
82
+ self.confirmed_at ||= Time.current # accepting the invite proves the email address
83
+ <% end -%>
84
+ save
85
+ end
86
+ end
@@ -0,0 +1,48 @@
1
+ module LockableConcern
2
+ extend ActiveSupport::Concern
3
+
4
+ MAXIMUM_ATTEMPTS = 5
5
+ UNLOCK_IN = 1.hour
6
+
7
+ class_methods do
8
+ def find_by_valid_unlock_token(token)
9
+ return if token.blank?
10
+
11
+ find_by(unlock_token: token)
12
+ end
13
+ end
14
+
15
+ # Locks expire on their own after UNLOCK_IN; the stale lock is cleared on the
16
+ # next successful sign-in by reset_failed_attempts!.
17
+ def locked?
18
+ locked_at.present? && locked_at.after?(UNLOCK_IN.ago)
19
+ end
20
+
21
+ def register_failed_attempt!
22
+ increment!(:failed_attempts)
23
+ lock! if failed_attempts >= MAXIMUM_ATTEMPTS && !locked?
24
+ end
25
+
26
+ def lock!
27
+ update!(locked_at: Time.current, unlock_token: generate_lockable_token)
28
+ send_unlock_instructions
29
+ end
30
+
31
+ def unlock!
32
+ update!(locked_at: nil, unlock_token: nil, failed_attempts: 0)
33
+ end
34
+
35
+ def reset_failed_attempts!
36
+ update!(failed_attempts: 0, locked_at: nil, unlock_token: nil) if failed_attempts.positive? || locked_at.present?
37
+ end
38
+
39
+ def send_unlock_instructions
40
+ UnlocksMailer.unlock_instructions(self).deliver_later
41
+ end
42
+
43
+ protected
44
+
45
+ def generate_lockable_token
46
+ SecureRandom.urlsafe_base64(32)
47
+ end
48
+ end
@@ -0,0 +1,33 @@
1
+ module RecoverableConcern
2
+ extend ActiveSupport::Concern
3
+
4
+ RESET_PASSWORD_TOKEN_EXPIRES_IN = 4.hours
5
+
6
+ class_methods do
7
+ def find_by_valid_reset_password_token(token)
8
+ return if token.blank?
9
+
10
+ user = find_by(reset_password_token: token)
11
+ user if user&.reset_password_sent_at&.after?(RESET_PASSWORD_TOKEN_EXPIRES_IN.ago)
12
+ end
13
+ end
14
+
15
+ def send_reset_password_instructions
16
+ generate_reset_password_token!
17
+ PasswordsMailer.reset(self).deliver_later
18
+ end
19
+
20
+ def generate_reset_password_token!
21
+ update!(reset_password_token: generate_recoverable_token, reset_password_sent_at: Time.current)
22
+ end
23
+
24
+ def clear_reset_password_token!
25
+ update!(reset_password_token: nil, reset_password_sent_at: nil)
26
+ end
27
+
28
+ protected
29
+
30
+ def generate_recoverable_token
31
+ SecureRandom.urlsafe_base64(32)
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ module RememberableConcern
2
+ extend ActiveSupport::Concern
3
+
4
+ def remembered?
5
+ remember_created_at.present?
6
+ end
7
+
8
+ def remember_me!
9
+ update!(remember_created_at: Time.current)
10
+ end
11
+
12
+ def forget_me!
13
+ update!(remember_created_at: nil) if remember_created_at.present?
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module TimeoutableConcern
2
+ extend ActiveSupport::Concern
3
+
4
+ TIMEOUT_IN = 30.minutes
5
+
6
+ def timedout?(last_activity_at)
7
+ last_activity_at.present? && last_activity_at.before?(TIMEOUT_IN.ago)
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module TrackableConcern
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ has_many :user_auths, dependent: :destroy
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ module ValidatableConcern
2
+ extend ActiveSupport::Concern
3
+
4
+ EMAIL_ADDRESS_REGEXP = /\A[^@\s]+@[^@\s]+\z/
5
+ PASSWORD_LENGTH = 6..72
6
+ PASSWORD_DIGIT_REGEXP = /\d+/
7
+ PASSWORD_UPPER_REGEXP = /[A-Z]+/
8
+ PASSWORD_LOWER_REGEXP = /[a-z]+/
9
+ PASSWORD_SPECIAL_REGEXP = /[^a-zA-Z\d\s]+/
10
+ PASSWORD_MINIMUM_COMPLEXITY = 2 # Minumum number of complexities to match to be valid
11
+
12
+ included do
13
+ validates :email_address, presence: true,
14
+ format: { with: EMAIL_ADDRESS_REGEXP },
15
+ uniqueness: { case_sensitive: false }
16
+ validates :password, length: { in: PASSWORD_LENGTH }, allow_nil: true
17
+
18
+ validate :password_complexity
19
+ end
20
+
21
+ protected
22
+
23
+ def password_complexity
24
+ return if password.blank? || valid_password_complexity?
25
+
26
+ errors.add(:password, "must meet password complexity standards")
27
+ end
28
+
29
+ def valid_password_complexity?
30
+ return false if password.blank?
31
+
32
+ conditions = [
33
+ PASSWORD_DIGIT_REGEXP.match?(password),
34
+ PASSWORD_UPPER_REGEXP.match?(password),
35
+ PASSWORD_LOWER_REGEXP.match?(password),
36
+ PASSWORD_SPECIAL_REGEXP.match?(password)
37
+ ]
38
+ conditions.count(true) >= PASSWORD_MINIMUM_COMPLEXITY
39
+ end
40
+ end
@@ -0,0 +1,18 @@
1
+ class UserAuth < ApplicationRecord
2
+ # user is optional so failed attempts against unknown email addresses still get recorded
3
+ belongs_to :user, optional: true
4
+
5
+ scope :successful, -> { where(success: true) }
6
+ scope :failed, -> { where(success: false) }
7
+
8
+ def self.record(user, request, success:, failure_reason: nil)
9
+ create!(
10
+ user: user,
11
+ ip: request.remote_ip,
12
+ user_agent: request.user_agent,
13
+ referrer: request.referer,
14
+ success: success,
15
+ failure_reason: failure_reason
16
+ )
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ <h1>Resend confirmation instructions</h1>
2
+
3
+ <%%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
4
+
5
+ <%%= form_with url: confirmations_path do |form| %>
6
+ <%%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %><br>
7
+ <%%= form.submit "Resend confirmation instructions" %>
8
+ <%% end %>
@@ -0,0 +1,6 @@
1
+ <p>
2
+ You can confirm your email address on
3
+ <%%= link_to "this confirmation page", confirmation_url(@user.confirmation_token) %>.
4
+
5
+ This link will expire in <%%= distance_of_time_in_words(0, ConfirmableConcern::CONFIRMATION_TOKEN_EXPIRES_IN) %>.
6
+ </p>
@@ -0,0 +1,4 @@
1
+ You can confirm your email address on
2
+ <%%= confirmation_url(@user.confirmation_token) %>
3
+
4
+ This link will expire in <%%= distance_of_time_in_words(0, ConfirmableConcern::CONFIRMATION_TOKEN_EXPIRES_IN) %>.
@@ -0,0 +1,9 @@
1
+ <h1>Accept your invitation</h1>
2
+
3
+ <%%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
4
+
5
+ <%%= form_with url: invitation_path(params[:token]), method: :patch do |form| %>
6
+ <%%= form.password_field :password, required: true, autofocus: true, autocomplete: "new-password", placeholder: "Choose a password", maxlength: 72 %><br>
7
+ <%%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Repeat the password", maxlength: 72 %><br>
8
+ <%%= form.submit "Set my password" %>
9
+ <%% end %>
@@ -0,0 +1,9 @@
1
+ <h1>Invite someone</h1>
2
+
3
+ <%%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
4
+ <%%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %>
5
+
6
+ <%%= form_with url: invitations_path do |form| %>
7
+ <%%= form.email_field :email_address, required: true, autofocus: true, placeholder: "Enter their email address" %><br>
8
+ <%%= form.submit "Send invitation" %>
9
+ <%% end %>
@@ -0,0 +1,6 @@
1
+ <p>
2
+ You have been invited. Set your password and activate your account on
3
+ <%%= link_to "this invitation page", edit_invitation_url(@user.invitation_token) %>.
4
+
5
+ This link will expire in <%%= distance_of_time_in_words(0, InvitableConcern::INVITATION_TOKEN_EXPIRES_IN) %>.
6
+ </p>
@@ -0,0 +1,4 @@
1
+ You have been invited. Set your password and activate your account on
2
+ <%%= edit_invitation_url(@user.invitation_token) %>
3
+
4
+ This link will expire in <%%= distance_of_time_in_words(0, InvitableConcern::INVITATION_TOKEN_EXPIRES_IN) %>.
@@ -0,0 +1,6 @@
1
+ <p>
2
+ You can reset your password on
3
+ <%%= link_to "this password reset page", edit_password_url(@user.reset_password_token) %>.
4
+
5
+ This link will expire in <%%= distance_of_time_in_words(0, RecoverableConcern::RESET_PASSWORD_TOKEN_EXPIRES_IN) %>.
6
+ </p>
@@ -0,0 +1,4 @@
1
+ You can reset your password on
2
+ <%%= edit_password_url(@user.reset_password_token) %>
3
+
4
+ This link will expire in <%%= distance_of_time_in_words(0, RecoverableConcern::RESET_PASSWORD_TOKEN_EXPIRES_IN) %>.
@@ -0,0 +1,22 @@
1
+ <h1>Your account</h1>
2
+
3
+ <%%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
4
+ <%%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %>
5
+
6
+ <%% if @user.errors.any? %>
7
+ <ul style="color:red">
8
+ <%% @user.errors.full_messages.each do |message| %>
9
+ <li><%%= message %></li>
10
+ <%% end %>
11
+ </ul>
12
+ <%% end %>
13
+
14
+ <%%= form_with url: registration_path, method: :patch do |form| %>
15
+ <%%= form.email_field :email_address, required: true, autocomplete: "username", value: @user.email_address %><br>
16
+ <%%= form.password_field :password, autocomplete: "new-password", placeholder: "New password (leave blank to keep current)", maxlength: 72 %><br>
17
+ <%%= form.password_field :password_confirmation, autocomplete: "new-password", placeholder: "Repeat new password", maxlength: 72 %><br>
18
+ <%%= form.submit "Update" %>
19
+ <%% end %>
20
+ <br>
21
+
22
+ <%%= button_to "Delete my account", registration_path, method: :delete, data: { turbo_confirm: "Are you sure?" } %>
@@ -0,0 +1,21 @@
1
+ <h1>Sign up</h1>
2
+
3
+ <%%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
4
+
5
+ <%% if @user.errors.any? %>
6
+ <ul style="color:red">
7
+ <%% @user.errors.full_messages.each do |message| %>
8
+ <li><%%= message %></li>
9
+ <%% end %>
10
+ </ul>
11
+ <%% end %>
12
+
13
+ <%%= form_with url: registration_path do |form| %>
14
+ <%%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: @user.email_address %><br>
15
+ <%%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Enter a password", maxlength: 72 %><br>
16
+ <%%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Repeat the password", maxlength: 72 %><br>
17
+ <%%= form.submit "Sign up" %>
18
+ <%% end %>
19
+ <br>
20
+
21
+ <%%= link_to "Sign in", new_session_path %>
@@ -0,0 +1,23 @@
1
+ <%%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
2
+ <%%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %>
3
+
4
+ <%%= form_with url: session_path do |form| %>
5
+ <%%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %><br>
6
+ <%%= form.password_field :password, required: true, autocomplete: "current-password", placeholder: "Enter your password", maxlength: 72 %><br>
7
+ <% if rememberable? -%>
8
+ <%%= form.check_box :remember_me %> <%%= form.label :remember_me, "Remember me" %><br>
9
+ <% end -%>
10
+ <%%= form.submit "Sign in" %>
11
+ <%% end %>
12
+ <br>
13
+
14
+ <%%= link_to "Forgot password?", new_password_path %>
15
+ <% if registerable? -%>
16
+ <br><%%= link_to "Sign up", new_registration_path %>
17
+ <% end -%>
18
+ <% if confirmable? -%>
19
+ <br><%%= link_to "Resend confirmation instructions", new_confirmation_path %>
20
+ <% end -%>
21
+ <% if lockable? -%>
22
+ <br><%%= link_to "Resend unlock instructions", new_unlock_path %>
23
+ <% end -%>