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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 305e121fa18342571e89ab2bee6cf11f34ba708c08c2c17fb85e5880b2f0ab56
4
+ data.tar.gz: b62fec7dddeaf09e159321f396b2723f3b5793f7c71fc72af3044c49987e6c86
5
+ SHA512:
6
+ metadata.gz: b35c71dd413c7e12398bdfd2b04f78e2e5669484254af2c9125958a69072ff2904d41bf858800f3853a48fb5aac20616425ae438407df8ed9045db742e6d0d8e
7
+ data.tar.gz: dfa8c8b76bff7b8d907bda610db59b3fd8fbbf580722f659f20cef9c2153b6ea56f69d531a879dded44d94820748074bb88f8da4f2397c61c2c3e778e68ff47a
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # rails_authentication
2
+
3
+ Devise-style features on top of Rails 8's built-in authentication generator.
4
+
5
+ Rails 8's `bin/rails generate authentication` gives you the essentials: a `User` model with
6
+ `has_secure_password`, database-backed sessions, sign-in/sign-out, and a password reset flow. This
7
+ gem extends that same command to also install:
8
+
9
+ | Feature | What it does |
10
+ | --- | --- |
11
+ | **Confirmable** | Emails confirmation instructions; blocks sign-in until the email address is confirmed |
12
+ | **Recoverable** | Password reset with DB-backed, revocable, expiring tokens (replaces the base stateless tokens) |
13
+ | **Registerable** | Sign-up, plus account editing and deletion |
14
+ | **Rememberable** | "Remember me" checkbox — persistent cookie only when checked |
15
+ | **Trackable** | Records every sign-in attempt (success and failure) in a `user_auths` table: IP, user agent, referrer, failure reason |
16
+ | **Timeoutable** | Expires sessions after 30 minutes of inactivity |
17
+ | **Validatable** | Email format/uniqueness, password length, and password complexity validations |
18
+ | **Lockable** | Locks the account after 5 failed attempts; unlock via email or automatically after 1 hour |
19
+ | **Invitable** | Invite users by email; they accept by choosing their own password |
20
+
21
+ Everything is **generated into your app** as plain, readable code — controllers, views, mailers,
22
+ migrations, and one model concern per feature. There is no runtime dependency on this gem: after
23
+ generating, the code is yours to edit.
24
+
25
+ This gem is meant to be installed temporarily. Install it long enough to run the generators and uninstall it after. If you keep it installed, keep it in the `development` group.
26
+
27
+ ## Installation
28
+
29
+ Requires Rails >= 8.0 and, for the email-driven features (Confirmable, Recoverable, Lockable,
30
+ Invitable), Action Mailer.
31
+
32
+ ```ruby
33
+ # Gemfile
34
+ group :development do
35
+ gem "rails_authentication"
36
+ end
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ```sh
42
+ bin/rails generate authentication
43
+ bin/rails db:migrate
44
+ ```
45
+
46
+ That single command runs Rails' built-in authentication generator first, then layers every feature
47
+ on top. Skip any feature you don't want:
48
+
49
+ ```sh
50
+ bin/rails generate authentication --skip-invitable --skip-trackable
51
+ ```
52
+
53
+ Available flags: `--skip-confirmable`, `--skip-recoverable`, `--skip-registerable`,
54
+ `--skip-rememberable`, `--skip-trackable`, `--skip-timeoutable`, `--skip-validatable`,
55
+ `--skip-lockable`, `--skip-invitable`, and `--reconfirmable` (Confirmable: postpone email address
56
+ changes until the new address is confirmed, via an `unconfirmed_email` column).
57
+
58
+ Each feature adds a single `include <Feature>Concern` line to `app/models/user.rb`; all of its
59
+ model behavior lives in `app/models/concerns/<feature>_concern.rb`. Tunables are plain constants in
60
+ the generated concerns — e.g. `TimeoutableConcern::TIMEOUT_IN`, `LockableConcern::MAXIMUM_ATTEMPTS`,
61
+ `LockableConcern::UNLOCK_IN`, `ConfirmableConcern::CONFIRMATION_TOKEN_EXPIRES_IN`,
62
+ `ValidatableConcern::PASSWORD_LENGTH`, `ValidatableConcern::PASSWORD_MINIMUM_COMPLEXITY` — edit them
63
+ there. Concerns keep the model clean.
64
+
65
+ ### Generated routes
66
+
67
+ ```
68
+ resource :registration, only: %i[ new create edit update destroy ]
69
+ resources :confirmations, only: %i[ new create show ], param: :token
70
+ resources :unlocks, only: %i[ new create show ], param: :token
71
+ resources :invitations, only: %i[ new create edit update ], param: :token
72
+ resource :session # from the base generator
73
+ resources :passwords, param: :token # from the base generator
74
+ ```
75
+
76
+ ### Notes
77
+
78
+ - **Existing users + Confirmable**: sign-in requires `confirmed_at`, so backfill it when adding
79
+ Confirmable to an app with existing users:
80
+ `User.update_all(confirmed_at: Time.current)`.
81
+ - **Recoverable** replaces the base generator's `PasswordsController`/`PasswordsMailer` (which use
82
+ stateless signed tokens) with DB-token versions. If you `--skip-recoverable`, the base flow is
83
+ left untouched.
84
+ - The generator overwrites the base generator's `SessionsController`, `Authentication` concern, and
85
+ sessions view with versions tailored to the features you selected. Run the generator once, up
86
+ front — re-running it after you've customized those files will prompt to overwrite them.
87
+
88
+ ## Future Plans
89
+
90
+ - OTP (Google Authenticator)
91
+ - Passwordless (code emailed)
92
+
93
+ ## Development
94
+
95
+ ```sh
96
+ bundle install
97
+ bundle exec rake spec:generators # generator specs (fast, no app boot)
98
+ bundle exec rake dummy:prepare # build spec/dummy by running the real generator
99
+ bundle exec rake spec:requests # request specs against spec/dummy
100
+ bundle exec rspec spec/generators/lockable_spec.rb:12 # a single example
101
+ ```
102
+
103
+ `spec/dummy` is generated (and gitignored), never hand-maintained, so the request specs always
104
+ exercise exactly what the templates produce.
105
+
106
+ ## License
107
+
108
+ MIT
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+ require_relative "features/confirmable"
6
+ require_relative "features/recoverable"
7
+ require_relative "features/registerable"
8
+ require_relative "features/rememberable"
9
+ require_relative "features/trackable"
10
+ require_relative "features/timeoutable"
11
+ require_relative "features/validatable"
12
+ require_relative "features/lockable"
13
+ require_relative "features/invitable"
14
+
15
+ module RailsAuthentication
16
+ module Generators
17
+ # Shadows Rails' built-in `bin/rails generate authentication` command: the explicit
18
+ # `authentication:authentication` namespace is checked before Rails' own
19
+ # `rails:authentication`, so the bare `authentication` invocation lands here.
20
+ # It runs the built-in generator first, then layers the feature set on top.
21
+ #
22
+ # The Ruby namespace is RailsAuthentication (not Authentication, which would give the
23
+ # same Thor namespace implicitly) because Rails 8's base generator creates an
24
+ # `Authentication` controller concern in the host app — a top-level constant this gem
25
+ # must not squat on.
26
+ class AuthenticationGenerator < Rails::Generators::Base
27
+ namespace "authentication:authentication"
28
+
29
+ FEATURES = %i[
30
+ confirmable recoverable registerable rememberable trackable
31
+ timeoutable validatable lockable invitable
32
+ ].freeze
33
+
34
+ include ActiveRecord::Generators::Migration
35
+
36
+ include Features::Confirmable
37
+ include Features::Recoverable
38
+ include Features::Registerable
39
+ include Features::Rememberable
40
+ include Features::Trackable
41
+ include Features::Timeoutable
42
+ include Features::Validatable
43
+ include Features::Lockable
44
+ include Features::Invitable
45
+
46
+ source_root File.expand_path("templates", __dir__)
47
+
48
+ FEATURES.each do |feature|
49
+ class_option :"skip_#{feature}", type: :boolean, default: false,
50
+ desc: "Skip #{feature}"
51
+ end
52
+
53
+ class_option :reconfirmable, type: :boolean, default: false,
54
+ desc: "Confirmable: postpone email address changes until reconfirmed (adds unconfirmed_email column)"
55
+
56
+ def install_base_authentication
57
+ say "Running Rails' built-in authentication generator", :green
58
+ Rails::Generators.invoke("rails:authentication", [], behavior: behavior, destination_root: destination_root)
59
+ end
60
+
61
+ def install_validatable
62
+ generate_validatable if validatable?
63
+ end
64
+
65
+ def install_registerable
66
+ generate_registerable if registerable?
67
+ end
68
+
69
+ def install_recoverable
70
+ generate_recoverable if recoverable?
71
+ end
72
+
73
+ def install_confirmable
74
+ generate_confirmable if confirmable?
75
+ end
76
+
77
+ def install_rememberable
78
+ generate_rememberable if rememberable?
79
+ end
80
+
81
+ def install_trackable
82
+ generate_trackable if trackable?
83
+ end
84
+
85
+ def install_timeoutable
86
+ generate_timeoutable if timeoutable?
87
+ end
88
+
89
+ def install_lockable
90
+ generate_lockable if lockable?
91
+ end
92
+
93
+ def install_invitable
94
+ generate_invitable if invitable?
95
+ end
96
+
97
+ # Confirmable, Rememberable, Trackable, Timeoutable, and Lockable all hook into the
98
+ # sign-in flow, so the base generator's session files are replaced with versions
99
+ # rendered from the enabled feature set. Overwriting is safe: the base copies were
100
+ # written moments ago by this same run.
101
+ def customize_session_layer
102
+ template "app/controllers/sessions_controller.rb", force: true
103
+ template "app/controllers/concerns/authentication.rb", force: true
104
+ template "app/views/sessions/new.html.erb", force: true
105
+ end
106
+
107
+ no_commands do
108
+ FEATURES.each do |feature|
109
+ define_method(:"#{feature}?") { !options[:"skip_#{feature}"] }
110
+ end
111
+
112
+ def reconfirmable?
113
+ confirmable? && options[:reconfirmable]
114
+ end
115
+
116
+ def include_concern_in_user(concern)
117
+ inject_into_class "app/models/user.rb", "User", " include #{concern}\n"
118
+ end
119
+
120
+ def migration_version
121
+ "[#{ActiveRecord::VERSION::STRING.to_f}]"
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuthentication
4
+ module Generators
5
+ module Features
6
+ module Confirmable
7
+ private
8
+ def generate_confirmable
9
+ template "app/models/concerns/confirmable_concern.rb"
10
+ include_concern_in_user "ConfirmableConcern"
11
+ migration_template "db/migrate/add_confirmable_to_users.rb", "db/migrate/add_confirmable_to_users.rb"
12
+ template "app/controllers/confirmations_controller.rb"
13
+ template "app/views/confirmations/new.html.erb"
14
+ route "resources :confirmations, only: %i[ new create show ], param: :token"
15
+
16
+ if defined?(ActionMailer::Railtie)
17
+ template "app/mailers/confirmations_mailer.rb"
18
+ template "app/views/confirmations_mailer/confirmation_instructions.html.erb"
19
+ template "app/views/confirmations_mailer/confirmation_instructions.text.erb"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuthentication
4
+ module Generators
5
+ module Features
6
+ module Invitable
7
+ private
8
+ def generate_invitable
9
+ template "app/models/concerns/invitable_concern.rb"
10
+ include_concern_in_user "InvitableConcern"
11
+ migration_template "db/migrate/add_invitable_to_users.rb", "db/migrate/add_invitable_to_users.rb"
12
+ template "app/controllers/invitations_controller.rb"
13
+ template "app/views/invitations/new.html.erb"
14
+ template "app/views/invitations/edit.html.erb"
15
+ route "resources :invitations, only: %i[ new create edit update ], param: :token"
16
+
17
+ if defined?(ActionMailer::Railtie)
18
+ template "app/mailers/invitations_mailer.rb"
19
+ template "app/views/invitations_mailer/invitation_instructions.html.erb"
20
+ template "app/views/invitations_mailer/invitation_instructions.text.erb"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuthentication
4
+ module Generators
5
+ module Features
6
+ module Lockable
7
+ private
8
+ def generate_lockable
9
+ template "app/models/concerns/lockable_concern.rb"
10
+ include_concern_in_user "LockableConcern"
11
+ migration_template "db/migrate/add_lockable_to_users.rb", "db/migrate/add_lockable_to_users.rb"
12
+ template "app/controllers/unlocks_controller.rb"
13
+ template "app/views/unlocks/new.html.erb"
14
+ route "resources :unlocks, only: %i[ new create show ], param: :token"
15
+
16
+ if defined?(ActionMailer::Railtie)
17
+ template "app/mailers/unlocks_mailer.rb"
18
+ template "app/views/unlocks_mailer/unlock_instructions.html.erb"
19
+ template "app/views/unlocks_mailer/unlock_instructions.text.erb"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuthentication
4
+ module Generators
5
+ module Features
6
+ module Recoverable
7
+ private
8
+ # Replaces the base generator's stateless generates_token_for password reset
9
+ # with DB-backed, revocable tokens.
10
+ def generate_recoverable
11
+ template "app/models/concerns/recoverable_concern.rb"
12
+ include_concern_in_user "RecoverableConcern"
13
+ migration_template "db/migrate/add_recoverable_to_users.rb", "db/migrate/add_recoverable_to_users.rb"
14
+ template "app/controllers/passwords_controller.rb", force: true
15
+
16
+ if defined?(ActionMailer::Railtie)
17
+ template "app/mailers/passwords_mailer.rb", force: true
18
+ template "app/views/passwords_mailer/reset.html.erb", force: true
19
+ template "app/views/passwords_mailer/reset.text.erb", force: true
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuthentication
4
+ module Generators
5
+ module Features
6
+ module Registerable
7
+ private
8
+ def generate_registerable
9
+ template "app/controllers/registrations_controller.rb"
10
+ template "app/views/registrations/new.html.erb"
11
+ template "app/views/registrations/edit.html.erb"
12
+ route "resource :registration, only: %i[ new create edit update destroy ]"
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuthentication
4
+ module Generators
5
+ module Features
6
+ module Rememberable
7
+ private
8
+ def generate_rememberable
9
+ template "app/models/concerns/rememberable_concern.rb"
10
+ include_concern_in_user "RememberableConcern"
11
+ migration_template "db/migrate/add_rememberable_to_users.rb", "db/migrate/add_rememberable_to_users.rb"
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuthentication
4
+ module Generators
5
+ module Features
6
+ module Timeoutable
7
+ private
8
+ # No migration: inactivity is measured against sessions.updated_at, which the
9
+ # base generator's sessions table already has.
10
+ def generate_timeoutable
11
+ template "app/models/concerns/timeoutable_concern.rb"
12
+ include_concern_in_user "TimeoutableConcern"
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuthentication
4
+ module Generators
5
+ module Features
6
+ module Trackable
7
+ private
8
+ def generate_trackable
9
+ template "app/models/concerns/trackable_concern.rb"
10
+ include_concern_in_user "TrackableConcern"
11
+ template "app/models/user_auth.rb"
12
+ migration_template "db/migrate/create_user_auths.rb", "db/migrate/create_user_auths.rb"
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAuthentication
4
+ module Generators
5
+ module Features
6
+ module Validatable
7
+ private
8
+ def generate_validatable
9
+ template "app/models/concerns/validatable_concern.rb"
10
+ include_concern_in_user "ValidatableConcern"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,96 @@
1
+ module Authentication
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action :require_authentication
6
+ helper_method :authenticated?, :current_user
7
+ end
8
+
9
+ class_methods do
10
+ def allow_unauthenticated_access(**options)
11
+ skip_before_action :require_authentication, **options
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def authenticated?
18
+ resume_session
19
+ end
20
+
21
+ def current_user
22
+ Current.user
23
+ end
24
+
25
+ def require_authentication
26
+ resume_session || request_authentication
27
+ end
28
+
29
+ def resume_session
30
+ Current.session ||= find_session_by_cookie
31
+ end
32
+
33
+ <% if timeoutable? -%>
34
+ def find_session_by_cookie
35
+ return unless cookies.signed[:session_id]
36
+ return unless session = Session.find_by(id: cookies.signed[:session_id])
37
+
38
+ <% if rememberable? -%>
39
+ if !session.user.remembered? && session.user.timedout?(session.updated_at)
40
+ <% else -%>
41
+ if session.user.timedout?(session.updated_at)
42
+ <% end -%>
43
+ session.destroy
44
+ cookies.delete(:session_id)
45
+ nil
46
+ else
47
+ session.touch
48
+ session
49
+ end
50
+ end
51
+ <% else -%>
52
+ def find_session_by_cookie
53
+ Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id]
54
+ end
55
+ <% end -%>
56
+
57
+ def request_authentication
58
+ session[:return_to_after_authenticating] = request.url
59
+ redirect_to new_session_path
60
+ end
61
+
62
+ def after_authentication_url
63
+ session.delete(:return_to_after_authenticating) || root_url
64
+ end
65
+
66
+ <% if rememberable? -%>
67
+ def start_new_session_for(user, remember: false)
68
+ user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
69
+ Current.session = session
70
+
71
+ if remember
72
+ user.remember_me!
73
+ cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }
74
+ else
75
+ user.forget_me!
76
+ cookies.signed[:session_id] = { value: session.id, httponly: true, same_site: :lax }
77
+ end
78
+ end
79
+ end
80
+ <% else -%>
81
+ def start_new_session_for(user)
82
+ user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
83
+ Current.session = session
84
+ cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }
85
+ end
86
+ end
87
+ <% end -%>
88
+
89
+ def terminate_session
90
+ <% if rememberable? -%>
91
+ Current.session.user.forget_me!
92
+ <% end -%>
93
+ Current.session.destroy
94
+ cookies.delete(:session_id)
95
+ end
96
+ end
@@ -0,0 +1,27 @@
1
+ class ConfirmationsController < ApplicationController
2
+ allow_unauthenticated_access
3
+ rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_confirmation_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
+ <% if reconfirmable? -%>
11
+ user.send_confirmation_instructions if user && (!user.confirmed? || user.unconfirmed_email.present?)
12
+ <% else -%>
13
+ user.send_confirmation_instructions if user && !user.confirmed?
14
+ <% end -%>
15
+
16
+ redirect_to new_session_path, notice: "Confirmation instructions sent (if user with that email address exists and is unconfirmed)."
17
+ end
18
+
19
+ def show
20
+ if user = User.find_by_valid_confirmation_token(params[:token])
21
+ user.confirm!
22
+ redirect_to new_session_path, notice: "Your email address has been confirmed. Please sign in."
23
+ else
24
+ redirect_to new_confirmation_path, alert: "Confirmation link is invalid or has expired."
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ class InvitationsController < ApplicationController
2
+ allow_unauthenticated_access only: %i[ edit update ]
3
+ before_action :set_user_by_token, only: %i[ edit update ]
4
+
5
+ def new
6
+ end
7
+
8
+ def create
9
+ user = User.invite!(params[:email_address], invited_by: Current.user)
10
+
11
+ if user.persisted?
12
+ redirect_to new_invitation_path, notice: "Invitation sent to #{user.email_address}."
13
+ else
14
+ redirect_to new_invitation_path, alert: user.errors.full_messages.to_sentence
15
+ end
16
+ end
17
+
18
+ def edit
19
+ end
20
+
21
+ def update
22
+ if @user.accept_invitation!(password: params[:password], password_confirmation: params[:password_confirmation])
23
+ start_new_session_for @user
24
+ redirect_to after_authentication_url, notice: "Welcome! Your account is ready."
25
+ else
26
+ redirect_to edit_invitation_path(params[:token]), alert: @user.errors.full_messages.to_sentence
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def set_user_by_token
33
+ @user = User.find_by_valid_invitation_token(params[:token])
34
+ redirect_to new_session_path, alert: "Invitation link is invalid or has expired." unless @user
35
+ end
36
+ end
@@ -0,0 +1,39 @@
1
+ class PasswordsController < ApplicationController
2
+ allow_unauthenticated_access
3
+ before_action :set_user_by_token, only: %i[ edit update ]
4
+ rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_password_path, alert: "Try again later." }
5
+
6
+ def new
7
+ end
8
+
9
+ def create
10
+ if user = User.find_by(email_address: params[:email_address])
11
+ user.send_reset_password_instructions
12
+ end
13
+
14
+ redirect_to new_session_path, notice: "Password reset instructions sent (if user with that email address exists)."
15
+ end
16
+
17
+ def edit
18
+ end
19
+
20
+ def update
21
+ if @user.update(params.permit(:password, :password_confirmation))
22
+ @user.clear_reset_password_token!
23
+ <% if lockable? -%>
24
+ @user.unlock!
25
+ <% end -%>
26
+ @user.sessions.destroy_all
27
+ redirect_to new_session_path, notice: "Password has been reset."
28
+ else
29
+ redirect_to edit_password_path(params[:token]), alert: "Passwords did not match."
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def set_user_by_token
36
+ @user = User.find_by_valid_reset_password_token(params[:token])
37
+ redirect_to new_password_path, alert: "Password reset link is invalid or has expired." unless @user
38
+ end
39
+ end