├── .documentation
├── .gitkeep
└── youtube_thumbnail.png
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .standard.yml
├── CHANGELOG.md
├── Gemfile
├── Gemfile.lock
├── MIT-LICENSE
├── README.md
├── Rakefile
├── app
├── assets
│ ├── config
│ │ └── rails_mvp_authentication_manifest.js
│ ├── images
│ │ └── rails_mvp_authentication
│ │ │ └── .keep
│ └── stylesheets
│ │ └── rails_mvp_authentication
│ │ └── application.css
├── controllers
│ ├── concerns
│ │ └── .keep
│ └── rails_mvp_authentication
│ │ └── application_controller.rb
├── helpers
│ └── rails_mvp_authentication
│ │ └── application_helper.rb
├── jobs
│ └── rails_mvp_authentication
│ │ └── application_job.rb
├── mailers
│ └── rails_mvp_authentication
│ │ └── application_mailer.rb
├── models
│ ├── concerns
│ │ └── .keep
│ └── rails_mvp_authentication
│ │ └── application_record.rb
└── views
│ └── layouts
│ └── rails_mvp_authentication
│ └── application.html.erb
├── bin
└── rails
├── config
└── routes.rb
├── lib
├── generators
│ └── rails_mvp_authentication
│ │ ├── USAGE
│ │ ├── install_generator.rb
│ │ └── templates
│ │ ├── README
│ │ ├── active_session.rb.tt
│ │ ├── active_sessions_controller.rb.tt
│ │ ├── authentication.rb.tt
│ │ ├── confirmations_controller.rb.tt
│ │ ├── current.rb.tt
│ │ ├── passwords_controller.rb.tt
│ │ ├── sessions_controller.rb.tt
│ │ ├── test
│ │ ├── controllers
│ │ │ ├── active_sessions_controller_test.rb.tt
│ │ │ ├── confirmations_controller_test.rb.tt
│ │ │ ├── passwords_controller_test.rb.tt
│ │ │ ├── sessions_controller_test.rb.tt
│ │ │ └── users_controller_test.rb.tt
│ │ ├── integration
│ │ │ ├── friendly_redirects_test.rb.tt
│ │ │ └── user_interface_test.rb.tt
│ │ ├── mailers
│ │ │ ├── previews
│ │ │ │ └── user_mailer_preview.rb.tt
│ │ │ └── user_mailer_test.rb.tt
│ │ ├── models
│ │ │ ├── active_session_test.rb.tt
│ │ │ └── user_test.rb.tt
│ │ └── system
│ │ │ └── logins_test.rb.tt
│ │ ├── user.rb.tt
│ │ ├── user_mailer.rb.tt
│ │ ├── users_controller.rb.tt
│ │ └── views
│ │ ├── confirmations
│ │ └── new.html.erb.tt
│ │ ├── passwords
│ │ ├── edit.html.erb.tt
│ │ └── new.html.erb.tt
│ │ ├── sessions
│ │ └── new.html.erb.tt
│ │ ├── user_mailer
│ │ ├── confirmation.html.erb.tt
│ │ ├── confirmation.text.erb.tt
│ │ ├── password_reset.html.erb.tt
│ │ └── password_reset.text.erb.tt
│ │ └── users
│ │ ├── edit.html.erb.tt
│ │ └── new.html.erb.tt
├── rails_mvp_authentication.rb
├── rails_mvp_authentication
│ ├── engine.rb
│ └── version.rb
└── tasks
│ └── rails_mvp_authentication_tasks.rake
├── rails_mvp_authentication.gemspec
└── test
├── controllers
└── .keep
├── dummy
├── Rakefile
├── app
│ ├── assets
│ │ ├── config
│ │ │ └── manifest.js
│ │ ├── images
│ │ │ └── .keep
│ │ └── stylesheets
│ │ │ └── application.css
│ ├── channels
│ │ └── application_cable
│ │ │ ├── channel.rb
│ │ │ └── connection.rb
│ ├── controllers
│ │ ├── application_controller.rb
│ │ └── concerns
│ │ │ └── .keep
│ ├── helpers
│ │ └── application_helper.rb
│ ├── jobs
│ │ └── application_job.rb
│ ├── mailers
│ │ └── application_mailer.rb
│ ├── models
│ │ ├── application_record.rb
│ │ └── concerns
│ │ │ └── .keep
│ └── views
│ │ └── layouts
│ │ ├── application.html.erb
│ │ ├── mailer.html.erb
│ │ └── mailer.text.erb
├── bin
│ ├── rails
│ ├── rake
│ └── setup
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── cable.yml
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── assets.rb
│ │ ├── content_security_policy.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ └── permissions_policy.rb
│ ├── locales
│ │ └── en.yml
│ ├── puma.rb
│ ├── routes.rb
│ └── storage.yml
├── db
│ └── schema.rb
├── lib
│ └── assets
│ │ └── .keep
├── log
│ └── .keep
└── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ └── favicon.ico
├── fixtures
└── files
│ └── .keep
├── helpers
└── .keep
├── integration
├── .keep
└── navigation_test.rb
├── lib
└── generators
│ └── rails_mvp_authentication
│ └── install_generator_test.rb
├── mailers
└── .keep
├── models
└── .keep
├── rails_mvp_authentication_test.rb
└── test_helper.rb
/.documentation/.gitkeep:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.documentation/youtube_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevepolitodesign/rails_mvp_authentication/a2a8cfe220b7b6eeeba918b23bbc12909dc2b494/.documentation/youtube_thumbnail.png
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Ruby
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | test:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Set up Ruby
17 | uses: ruby/setup-ruby@v1
18 | with:
19 | ruby-version: '2.7.0'
20 | - name: Install dependencies
21 | run: bundle install
22 | - name: Run Standard
23 | run: bundle exec standardrb
24 | - name: Run tests
25 | run: bundle exec rails test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /doc/
3 | /log/*.log
4 | /pkg/
5 | /tmp/
6 | /test/dummy/db/*.sqlite3
7 | /test/dummy/db/*.sqlite3-*
8 | /test/dummy/log/*.log
9 | /test/dummy/storage/
10 | /test/dummy/tmp/
11 |
--------------------------------------------------------------------------------
/.standard.yml:
--------------------------------------------------------------------------------
1 | ignore:
2 | - test/dummy/config/environments/production.rb
3 | - test/dummy/config/puma
4 | - test/dummy/config/puma.rb
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.0.0 (February 18, 2022) ##
2 |
3 | * Initial release 🚀
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3 |
4 | # Specify your gem's dependencies in rails_mvp_authentication.gemspec.
5 | gemspec
6 |
7 | group :development do
8 | gem "sprockets-rails", "~> 3.4", ">= 3.4.2"
9 | gem "sqlite3"
10 | end
11 |
12 | group :development, :test do
13 | gem "standard", "~> 1.7"
14 | gem "bcrypt", "~> 3.1.7"
15 | end
16 |
17 | # Start debugger with binding.b -- Read more: https://github.com/ruby/debug
18 | # gem "debug", ">= 1.0.0", group: %i[ development test ]
19 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | rails_mvp_authentication (1.0.0)
5 | rails (>= 7.0.0)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | actioncable (7.0.1)
11 | actionpack (= 7.0.1)
12 | activesupport (= 7.0.1)
13 | nio4r (~> 2.0)
14 | websocket-driver (>= 0.6.1)
15 | actionmailbox (7.0.1)
16 | actionpack (= 7.0.1)
17 | activejob (= 7.0.1)
18 | activerecord (= 7.0.1)
19 | activestorage (= 7.0.1)
20 | activesupport (= 7.0.1)
21 | mail (>= 2.7.1)
22 | net-imap
23 | net-pop
24 | net-smtp
25 | actionmailer (7.0.1)
26 | actionpack (= 7.0.1)
27 | actionview (= 7.0.1)
28 | activejob (= 7.0.1)
29 | activesupport (= 7.0.1)
30 | mail (~> 2.5, >= 2.5.4)
31 | net-imap
32 | net-pop
33 | net-smtp
34 | rails-dom-testing (~> 2.0)
35 | actionpack (7.0.1)
36 | actionview (= 7.0.1)
37 | activesupport (= 7.0.1)
38 | rack (~> 2.0, >= 2.2.0)
39 | rack-test (>= 0.6.3)
40 | rails-dom-testing (~> 2.0)
41 | rails-html-sanitizer (~> 1.0, >= 1.2.0)
42 | actiontext (7.0.1)
43 | actionpack (= 7.0.1)
44 | activerecord (= 7.0.1)
45 | activestorage (= 7.0.1)
46 | activesupport (= 7.0.1)
47 | globalid (>= 0.6.0)
48 | nokogiri (>= 1.8.5)
49 | actionview (7.0.1)
50 | activesupport (= 7.0.1)
51 | builder (~> 3.1)
52 | erubi (~> 1.4)
53 | rails-dom-testing (~> 2.0)
54 | rails-html-sanitizer (~> 1.1, >= 1.2.0)
55 | activejob (7.0.1)
56 | activesupport (= 7.0.1)
57 | globalid (>= 0.3.6)
58 | activemodel (7.0.1)
59 | activesupport (= 7.0.1)
60 | activerecord (7.0.1)
61 | activemodel (= 7.0.1)
62 | activesupport (= 7.0.1)
63 | activestorage (7.0.1)
64 | actionpack (= 7.0.1)
65 | activejob (= 7.0.1)
66 | activerecord (= 7.0.1)
67 | activesupport (= 7.0.1)
68 | marcel (~> 1.0)
69 | mini_mime (>= 1.1.0)
70 | activesupport (7.0.1)
71 | concurrent-ruby (~> 1.0, >= 1.0.2)
72 | i18n (>= 1.6, < 2)
73 | minitest (>= 5.1)
74 | tzinfo (~> 2.0)
75 | ast (2.4.2)
76 | bcrypt (3.1.16)
77 | builder (3.2.4)
78 | concurrent-ruby (1.1.9)
79 | crass (1.0.6)
80 | digest (3.1.0)
81 | erubi (1.10.0)
82 | globalid (1.0.0)
83 | activesupport (>= 5.0)
84 | i18n (1.9.1)
85 | concurrent-ruby (~> 1.0)
86 | io-wait (0.2.1)
87 | loofah (2.13.0)
88 | crass (~> 1.0.2)
89 | nokogiri (>= 1.5.9)
90 | mail (2.7.1)
91 | mini_mime (>= 0.1.1)
92 | marcel (1.0.2)
93 | method_source (1.0.0)
94 | mini_mime (1.1.2)
95 | minitest (5.15.0)
96 | net-imap (0.2.3)
97 | digest
98 | net-protocol
99 | strscan
100 | net-pop (0.1.1)
101 | digest
102 | net-protocol
103 | timeout
104 | net-protocol (0.1.2)
105 | io-wait
106 | timeout
107 | net-smtp (0.3.1)
108 | digest
109 | net-protocol
110 | timeout
111 | nio4r (2.5.8)
112 | nokogiri (1.13.1-arm64-darwin)
113 | racc (~> 1.4)
114 | parallel (1.21.0)
115 | parser (3.1.0.0)
116 | ast (~> 2.4.1)
117 | racc (1.6.0)
118 | rack (2.2.3)
119 | rack-test (1.1.0)
120 | rack (>= 1.0, < 3)
121 | rails (7.0.1)
122 | actioncable (= 7.0.1)
123 | actionmailbox (= 7.0.1)
124 | actionmailer (= 7.0.1)
125 | actionpack (= 7.0.1)
126 | actiontext (= 7.0.1)
127 | actionview (= 7.0.1)
128 | activejob (= 7.0.1)
129 | activemodel (= 7.0.1)
130 | activerecord (= 7.0.1)
131 | activestorage (= 7.0.1)
132 | activesupport (= 7.0.1)
133 | bundler (>= 1.15.0)
134 | railties (= 7.0.1)
135 | rails-dom-testing (2.0.3)
136 | activesupport (>= 4.2.0)
137 | nokogiri (>= 1.6)
138 | rails-html-sanitizer (1.4.2)
139 | loofah (~> 2.3)
140 | railties (7.0.1)
141 | actionpack (= 7.0.1)
142 | activesupport (= 7.0.1)
143 | method_source
144 | rake (>= 12.2)
145 | thor (~> 1.0)
146 | zeitwerk (~> 2.5)
147 | rainbow (3.1.1)
148 | rake (13.0.6)
149 | regexp_parser (2.2.0)
150 | rexml (3.2.5)
151 | rubocop (1.25.0)
152 | parallel (~> 1.10)
153 | parser (>= 3.1.0.0)
154 | rainbow (>= 2.2.2, < 4.0)
155 | regexp_parser (>= 1.8, < 3.0)
156 | rexml
157 | rubocop-ast (>= 1.15.1, < 2.0)
158 | ruby-progressbar (~> 1.7)
159 | unicode-display_width (>= 1.4.0, < 3.0)
160 | rubocop-ast (1.15.1)
161 | parser (>= 3.0.1.1)
162 | rubocop-performance (1.13.2)
163 | rubocop (>= 1.7.0, < 2.0)
164 | rubocop-ast (>= 0.4.0)
165 | ruby-progressbar (1.11.0)
166 | sprockets (4.0.2)
167 | concurrent-ruby (~> 1.0)
168 | rack (> 1, < 3)
169 | sprockets-rails (3.4.2)
170 | actionpack (>= 5.2)
171 | activesupport (>= 5.2)
172 | sprockets (>= 3.0.0)
173 | sqlite3 (1.4.2)
174 | standard (1.7.0)
175 | rubocop (= 1.25.0)
176 | rubocop-performance (= 1.13.2)
177 | strscan (3.0.1)
178 | thor (1.2.1)
179 | timeout (0.2.0)
180 | tzinfo (2.0.4)
181 | concurrent-ruby (~> 1.0)
182 | unicode-display_width (2.1.0)
183 | websocket-driver (0.7.5)
184 | websocket-extensions (>= 0.1.0)
185 | websocket-extensions (0.1.5)
186 | zeitwerk (2.5.4)
187 |
188 | PLATFORMS
189 | arm64-darwin-21
190 |
191 | DEPENDENCIES
192 | bcrypt (~> 3.1.7)
193 | rails_mvp_authentication!
194 | sprockets-rails (~> 3.4, >= 3.4.2)
195 | sqlite3
196 | standard (~> 1.7)
197 |
198 | BUNDLED WITH
199 | 2.2.32
200 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2022 Steve Polito
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🔐 Rails MVP Authentication
2 |
3 | An authentication generator for Rails 7. Based on the [step-by-step guide on how to build your own authentication system in Rails from scratch](https://github.com/stevepolitodesign/rails-authentication-from-scratch).
4 |
5 | ## 🎬 Demo
6 |
7 |
8 |
9 |
10 |
11 | ## 🚀 Installation
12 |
13 | Add this line to your application's Gemfile:
14 |
15 | ```ruby
16 | gem "rails_mvp_authentication"
17 | ```
18 |
19 | And then execute:
20 | ```bash
21 | bundle
22 | ```
23 |
24 | Or install it yourself as:
25 | ```bash
26 | gem install rails_mvp_authentication
27 | ```
28 |
29 | Then run the installation command:
30 | ```bash
31 | rails g rails_mvp_authentication:install
32 | ```
33 |
34 | Once installed make follow these steps:
35 |
36 | 1. Run `bundle install` to install [bcrypt](https://rubygems.org/gems/bcrypt/)
37 | 2. Run `rails db:migrate` to add the `users` and `active_sessions` tables
38 | 3. Add a root path in `config/routes.rb`
39 | 4. Ensure you have flash messages in `app/views/layouts/application.html.erb`
40 |
41 | ```html+erb
42 |
<%= notice %>
43 |<%= alert %>
44 | ``` 45 | 46 | After completing these steps you can uninstall the gem: 47 | 48 | ```bash 49 | bundle remove "rails_mvp_authentication" --install 50 | ``` 51 | 52 | ## 📝 Features 53 | 54 | - Requires a user to confirm their email address before they can log in. 55 | - Allows a user to remain logged into the application even if they exit their browser. 56 | - Allows a user to have multiple sessions. This gives users the ability to log out of all sessions at once. This also makes it easy to detect suspicious login activity. 57 | - Allows a user to change their email address. 58 | - Allows a user to recover their account if they forget their password. 59 | - Requires users to submit their password anytime they're chaning their account information. 60 | 61 | ## 🔨 Usage 62 | 63 | The following methods are automatically included in the corresponding generated files. 64 | 65 | ### Controller Methods 66 | 67 | #### authenticate_user! 68 | 69 | Redirects the visitor to the `login_path` if they're not logged in. Useful for preventing an anonymous user from accessing a page intended for an authenticated user. 70 | 71 | #### current_user 72 | 73 | Returns an instance of `User` if there's one in the session. Othwerwise returns `nil`. 74 | 75 | #### forget_active_session 76 | 77 | Deletes the `:remember_token` cookie. For added security, the associated `active_session` should be deleted too. 78 | 79 | #### login(user) 80 | 81 | [Resets](https://api.rubyonrails.org/classes/ActionDispatch/Request.html#method-i-reset_session) the session and then creates a new `active_session` with on the `user` that was passed in. Stores the `id` of the `active_session` in the `session`. Returns the new `active_session`. 82 | 83 | #### logout 84 | 85 | [Resets](https://api.rubyonrails.org/classes/ActionDispatch/Request.html#method-i-reset_session) the session and deletes the associated `active_session` record. 86 | 87 | #### user_signed_in? 88 | 89 | Returns `true` if `current_user` does not return `nil`. Othwerwise returns `false`. 90 | 91 | #### redirect_if_authenticated 92 | 93 | Redirects the user to the `root_path` if the user is logged in. Useful for keeping a user from accessing a page intended for an anonymous user. 94 | 95 | #### remember(active_session) 96 | 97 | Creates a cookie to store the value of the `remember_token` from the `active_session` that was passed in. 98 | 99 | ### View Helpers 100 | 101 | #### current_user 102 | 103 | Returns an instance of `User` if there's one in the session. Othwerwise returns `nil`. 104 | 105 | #### user_signed_in? 106 | 107 | Returns `true` if `current_user` does not return `nil`. Othwerwise returns `false`. 108 | 109 | ### User Model 110 | 111 | #### self.authenticate_by(attributes) 112 | 113 | A copy of the [authenticate_by](https://edgeapi.rubyonrails.org/classes/ActiveRecord/SecurePassword/ClassMethods.html#method-i-authenticate_by) class method that is set to ship in rails 7.1 114 | 115 | #### confirm! 116 | 117 | Sets the `confirmed_at` column to `Time.current`. Updates the `email` column if reconfirming a new email address. Returns `true` or `false`. 118 | 119 | #### confirmed? 120 | 121 | Returns `true` or `false` based on if the `confirmed_at` column is present. 122 | 123 | #### confirmable_email 124 | 125 | Returns the value of the `email` column if the `unconfirmed_email` column is empty. Otherwise, the value of `unconfirmed_email` is returned. 126 | 127 | #### generate_confirmation_token 128 | 129 | Generates a [signed_id](https://api.rubyonrails.org/classes/ActiveRecord/SignedId.html#method-i-signed_id) used in the confirmation mailer. 130 | 131 | #### generate_password_reset_token 132 | 133 | Generates a [signed_id](https://api.rubyonrails.org/classes/ActiveRecord/SignedId.html#method-i-signed_id) used in the password reset mailer. 134 | 135 | #### send_confirmation_email! 136 | 137 | Send a confirmation email to the user. 138 | 139 | #### send_password_reset_email! 140 | 141 | Send a password reset email to the user. 142 | 143 | #### reconfirming? 144 | 145 | Returns `true` if there's a value for `unconfirmed_email`. Otherwise `false` is returned. 146 | 147 | #### unconfirmed? 148 | 149 | Returns `true` if there's no value for `confirmed_at`. Otherwise `false` is returned. 150 | 151 | #### unconfirmed_or_reconfirming? 152 | 153 | Returns `true` if the user is unconfirmed or reconfirming a new email address. Otherwise `false` is returned. 154 | 155 | ### Test Helpers 156 | 157 | #### current_user 158 | 159 | Returns an instance of `User` if there's one in the test session. Othwerwise returns `nil`. 160 | 161 | #### login(user, remember_user: nil) 162 | 163 | Creates a `post` request to the `login_path`. Simulates a real login. 164 | 165 | #### logout 166 | 167 | Deletes the `current_active_session_id` test session. Simulates a login. 168 | 169 | ## ⚖️ Benefits 170 | 171 | What makes this gem _different_ (not better) from [devise](https://github.com/heartcombo/devise), [clearance](https://github.com/thoughtbot/clearance/), etc? 172 | 173 | 1. This gem is less of an [engine](https://guides.rubyonrails.org/engines.html) and more of a [generator](https://guides.rubyonrails.org/generators.html). It generates all necessary models, views, controllers, mailers, and migrations. This means you have complete control over your authentication system and don't have to worry about learning a new DSL or API. 174 | 2. It also generates tests. That way you can ship with confidence if and when you decide to change how your authentication system works. 175 | 3. It utilizes modern core features of Rails, such as [ActiveSupport::CurrentAttributes](https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html) and [Active Record Signed Id](https://api.rubyonrails.org/classes/ActiveRecord/SignedId.html#method-i-signed_id), [has_secure_password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password) and [has_secure_token](https://api.rubyonrails.org/classes/ActiveRecord/SecureToken/ClassMethods.html#method-i-has_secure_token). 176 | 4. It stores the session in the database. This gives users the ability to log out of all sessions at once. This also makes it easy to detect suspicious login activity. 177 | 178 | ## 🙏 Contributing 179 | 180 | If you'd like to open a PR please make sure the following things pass: 181 | 182 | ```ruby 183 | bin/rails test 184 | bundle exec standardrb 185 | ``` 186 | ## 📜 License 187 | 188 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 189 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | 3 | APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__) 4 | load "rails/tasks/engine.rake" 5 | 6 | load "rails/tasks/statistics.rake" 7 | 8 | require "bundler/gem_tasks" 9 | -------------------------------------------------------------------------------- /app/assets/config/rails_mvp_authentication_manifest.js: -------------------------------------------------------------------------------- 1 | //= link_directory ../stylesheets/rails_mvp_authentication .css 2 | -------------------------------------------------------------------------------- /app/assets/images/rails_mvp_authentication/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevepolitodesign/rails_mvp_authentication/a2a8cfe220b7b6eeeba918b23bbc12909dc2b494/app/assets/images/rails_mvp_authentication/.keep -------------------------------------------------------------------------------- /app/assets/stylesheets/rails_mvp_authentication/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevepolitodesign/rails_mvp_authentication/a2a8cfe220b7b6eeeba918b23bbc12909dc2b494/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/rails_mvp_authentication/application_controller.rb: -------------------------------------------------------------------------------- 1 | module RailsMvpAuthentication 2 | class ApplicationController < ActionController::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/rails_mvp_authentication/application_helper.rb: -------------------------------------------------------------------------------- 1 | module RailsMvpAuthentication 2 | module ApplicationHelper 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/jobs/rails_mvp_authentication/application_job.rb: -------------------------------------------------------------------------------- 1 | module RailsMvpAuthentication 2 | class ApplicationJob < ActiveJob::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/mailers/rails_mvp_authentication/application_mailer.rb: -------------------------------------------------------------------------------- 1 | module RailsMvpAuthentication 2 | class ApplicationMailer < ActionMailer::Base 3 | default from: "from@example.com" 4 | layout "mailer" 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevepolitodesign/rails_mvp_authentication/a2a8cfe220b7b6eeeba918b23bbc12909dc2b494/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/rails_mvp_authentication/application_record.rb: -------------------------------------------------------------------------------- 1 | module RailsMvpAuthentication 2 | class ApplicationRecord < ActiveRecord::Base 3 | self.abstract_class = true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/layouts/rails_mvp_authentication/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |<%= notice %>
11 |<%= alert %>
12 | 13 | ========================================= -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/active_session.rb.tt: -------------------------------------------------------------------------------- 1 | class ActiveSession < ApplicationRecord 2 | belongs_to :user 3 | 4 | has_secure_token :remember_token 5 | end 6 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/active_sessions_controller.rb.tt: -------------------------------------------------------------------------------- 1 | class ActiveSessionsController < ApplicationController 2 | before_action :authenticate_user! 3 | 4 | def destroy 5 | @active_session = current_user.active_sessions.find(params[:id]) 6 | 7 | @active_session.destroy 8 | 9 | if current_user 10 | redirect_to account_path, notice: "Session deleted." 11 | else 12 | forget_active_session 13 | reset_session 14 | redirect_to root_path, notice: "Signed out." 15 | end 16 | end 17 | 18 | def destroy_all 19 | forget_active_session 20 | current_user.active_sessions.destroy_all 21 | reset_session 22 | 23 | redirect_to root_path, notice: "Signed out." 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/authentication.rb.tt: -------------------------------------------------------------------------------- 1 | module Authentication 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | before_action :current_user 6 | helper_method :current_user 7 | helper_method :user_signed_in? 8 | end 9 | 10 | def authenticate_user! 11 | store_location 12 | redirect_to login_path, alert: "You need to login to access that page." unless user_signed_in? 13 | end 14 | 15 | def login(user) 16 | reset_session 17 | active_session = user.active_sessions.create!(user_agent: request.user_agent, ip_address: request.ip) 18 | session[:current_active_session_id] = active_session.id 19 | 20 | active_session 21 | end 22 | 23 | def forget_active_session 24 | cookies.delete :remember_token 25 | end 26 | 27 | def logout 28 | active_session = ActiveSession.find_by(id: session[:current_active_session_id]) 29 | reset_session 30 | active_session.destroy! if active_session.present? 31 | end 32 | 33 | def redirect_if_authenticated 34 | redirect_to root_path, alert: "You are already logged in." if user_signed_in? 35 | end 36 | 37 | def remember(active_session) 38 | cookies.permanent.encrypted[:remember_token] = active_session.remember_token 39 | end 40 | 41 | private 42 | 43 | def current_user 44 | Current.user = if session[:current_active_session_id].present? 45 | ActiveSession.find_by(id: session[:current_active_session_id])&.user 46 | elsif cookies.permanent.encrypted[:remember_token].present? 47 | ActiveSession.find_by(remember_token: cookies.permanent.encrypted[:remember_token])&.user 48 | end 49 | end 50 | 51 | def user_signed_in? 52 | Current.user.present? 53 | end 54 | 55 | def store_location 56 | session[:user_return_to] = request.original_url if request.get? && request.local? 57 | end 58 | end -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/confirmations_controller.rb.tt: -------------------------------------------------------------------------------- 1 | class ConfirmationsController < ApplicationController 2 | before_action :redirect_if_authenticated, only: [:create, :new] 3 | 4 | def create 5 | @user = User.find_by(email: params[:user][:email].downcase) 6 | 7 | if @user.present? && @user.unconfirmed? 8 | @user.send_confirmation_email! 9 | redirect_to root_path, notice: "Check your email for confirmation instructions." 10 | else 11 | redirect_to new_confirmation_path, alert: "We could not find a user with that email or that email has already been confirmed." 12 | end 13 | end 14 | 15 | def edit 16 | @user = User.find_signed(params[:confirmation_token], purpose: :confirm_email) 17 | if @user.present? && @user.unconfirmed_or_reconfirming? 18 | if @user.confirm! 19 | login @user 20 | redirect_to root_path, notice: "Your account has been confirmed." 21 | else 22 | redirect_to new_confirmation_path, alert: "Something went wrong." 23 | end 24 | else 25 | redirect_to new_confirmation_path, alert: "Invalid token." 26 | end 27 | end 28 | 29 | def new 30 | @user = User.new 31 | end 32 | end -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/current.rb.tt: -------------------------------------------------------------------------------- 1 | class Current < ActiveSupport::CurrentAttributes 2 | attribute :user 3 | end -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/passwords_controller.rb.tt: -------------------------------------------------------------------------------- 1 | class PasswordsController < ApplicationController 2 | before_action :redirect_if_authenticated 3 | 4 | def create 5 | @user = User.find_by(email: params[:user][:email].downcase) 6 | if @user.present? 7 | if @user.confirmed? 8 | @user.send_password_reset_email! 9 | redirect_to root_path, notice: "If that user exists we've sent instructions to their email." 10 | else 11 | redirect_to new_confirmation_path, alert: "Please confirm your email first." 12 | end 13 | else 14 | redirect_to root_path, notice: "If that user exists we've sent instructions to their email." 15 | end 16 | end 17 | 18 | def edit 19 | @user = User.find_signed(params[:password_reset_token], purpose: :reset_password) 20 | if @user.present? && @user.unconfirmed? 21 | redirect_to new_confirmation_path, alert: "You must confirm your email before you can sign in." 22 | elsif @user.nil? 23 | redirect_to new_password_path, alert: "Invalid or expired token." 24 | end 25 | end 26 | 27 | def new 28 | end 29 | 30 | def update 31 | @user = User.find_signed(params[:password_reset_token], purpose: :reset_password) 32 | if @user 33 | if @user.unconfirmed? 34 | redirect_to new_confirmation_path, alert: "You must confirm your email before you can sign in." 35 | elsif @user.update(password_params) 36 | redirect_to login_path, notice: "Sign in." 37 | else 38 | flash.now[:alert] = @user.errors.full_messages.to_sentence 39 | render :edit, status: :unprocessable_entity 40 | end 41 | else 42 | flash.now[:alert] = "Invalid or expired token." 43 | render :new, status: :unprocessable_entity 44 | end 45 | end 46 | 47 | private 48 | 49 | def password_params 50 | params.require(:user).permit(:password, :password_confirmation) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/sessions_controller.rb.tt: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | before_action :redirect_if_authenticated, only: [:create, :new] 3 | before_action :authenticate_user!, only: [:destroy] 4 | 5 | def create 6 | @user = User.authenticate_by(email: params[:user][:email].downcase, password: params[:user][:password]) 7 | if @user 8 | if @user.unconfirmed? 9 | redirect_to new_confirmation_path, alert: "Incorrect email or password." 10 | else 11 | after_login_path = session[:user_return_to] || root_path 12 | active_session = login @user 13 | remember(active_session) if params[:user][:remember_me] == "1" 14 | redirect_to after_login_path, notice: "Signed in." 15 | end 16 | else 17 | flash.now[:alert] = "Incorrect email or password." 18 | render :new, status: :unprocessable_entity 19 | end 20 | end 21 | 22 | def destroy 23 | forget_active_session 24 | logout 25 | redirect_to root_path, notice: "Signed out." 26 | end 27 | 28 | def new 29 | end 30 | end -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/test/controllers/active_sessions_controller_test.rb.tt: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ActiveSessionsControllerTest < ActionDispatch::IntegrationTest 4 | setup do 5 | @confirmed_user = User.create!(email: "confirmed_user@example.com", password: "password", password_confirmation: "password", confirmed_at: Time.current) 6 | end 7 | 8 | test "should destroy all active sessions" do 9 | login @confirmed_user 10 | @confirmed_user.active_sessions.create! 11 | 12 | assert_difference("ActiveSession.count", -2) do 13 | delete destroy_all_active_sessions_path 14 | end 15 | 16 | assert_redirected_to root_path 17 | assert_nil current_user 18 | assert_not_nil flash[:notice] 19 | end 20 | 21 | test "should destroy all active sessions and forget active sessions" do 22 | login @confirmed_user, remember_user: true 23 | @confirmed_user.active_sessions.create! 24 | 25 | assert_difference("ActiveSession.count", -2) do 26 | delete destroy_all_active_sessions_path 27 | end 28 | 29 | assert_nil current_user 30 | assert cookies[:remember_token].blank? 31 | end 32 | 33 | test "should destroy another session" do 34 | login @confirmed_user 35 | @confirmed_user.active_sessions.create! 36 | 37 | assert_difference("ActiveSession.count", -1) do 38 | delete active_session_path(@confirmed_user.active_sessions.last) 39 | end 40 | 41 | assert_redirected_to account_path 42 | assert_not_nil current_user 43 | assert_not_nil flash[:notice] 44 | end 45 | 46 | test "should destroy current session" do 47 | login @confirmed_user 48 | 49 | assert_difference("ActiveSession.count", -1) do 50 | delete active_session_path(@confirmed_user.active_sessions.last) 51 | end 52 | 53 | assert_redirected_to root_path 54 | assert_nil current_user 55 | assert_not_nil flash[:notice] 56 | end 57 | 58 | test "should destroy current session and forget current active session" do 59 | login @confirmed_user, remember_user: true 60 | 61 | assert_difference("ActiveSession.count", -1) do 62 | delete active_session_path(@confirmed_user.active_sessions.last) 63 | end 64 | 65 | assert_nil current_user 66 | assert cookies[:remember_token].blank? 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/test/controllers/confirmations_controller_test.rb.tt: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ConfirmationsControllerTest < ActionDispatch::IntegrationTest 4 | setup do 5 | @reconfirmed_user = User.create!(email: "reconfirmed_user@example.com", password: "password", password_confirmation: "password", confirmed_at: 1.week.ago, unconfirmed_email: "unconfirmed_email@example.com") 6 | @confirmed_user = User.create!(email: "confirmed_user@example.com", password: "password", password_confirmation: "password", confirmed_at: 1.week.ago) 7 | @unconfirmed_user = User.create!(email: "unconfirmed_user@example.com", password: "password", password_confirmation: "password") 8 | end 9 | 10 | test "should confirm unconfirmed user" do 11 | freeze_time do 12 | confirmation_token = @unconfirmed_user.generate_confirmation_token 13 | 14 | get edit_confirmation_path(confirmation_token) 15 | 16 | assert @unconfirmed_user.reload.confirmed? 17 | assert_equal Time.now, @unconfirmed_user.confirmed_at 18 | assert_redirected_to root_path 19 | assert_not_nil flash[:notice] 20 | end 21 | end 22 | 23 | test "should reconfirm confirmed user" do 24 | unconfirmed_email = @reconfirmed_user.unconfirmed_email 25 | 26 | freeze_time do 27 | confirmation_token = @reconfirmed_user.generate_confirmation_token 28 | 29 | get edit_confirmation_path(confirmation_token) 30 | 31 | assert @reconfirmed_user.reload.confirmed? 32 | assert_equal Time.current, @reconfirmed_user.reload.confirmed_at 33 | assert_equal unconfirmed_email, @reconfirmed_user.reload.email 34 | assert_nil @reconfirmed_user.reload.unconfirmed_email 35 | assert_redirected_to root_path 36 | assert_not_nil flash[:notice] 37 | end 38 | end 39 | 40 | test "should not update email address if already taken" do 41 | original_email = @reconfirmed_user.email 42 | @reconfirmed_user.update(unconfirmed_email: @confirmed_user.email) 43 | 44 | freeze_time do 45 | confirmation_token = @reconfirmed_user.generate_confirmation_token 46 | 47 | get edit_confirmation_path(confirmation_token) 48 | 49 | assert_equal original_email, @reconfirmed_user.reload.email 50 | assert_redirected_to new_confirmation_path 51 | assert_not_nil flash[:alert] 52 | end 53 | end 54 | 55 | test "should redirect if confirmation link expired" do 56 | confirmation_token = @unconfirmed_user.generate_confirmation_token 57 | 58 | travel_to 601.seconds.from_now do 59 | get edit_confirmation_path(confirmation_token) 60 | 61 | assert_nil @unconfirmed_user.reload.confirmed_at 62 | assert_not @unconfirmed_user.reload.confirmed? 63 | assert_redirected_to new_confirmation_path 64 | assert_not_nil flash[:alert] 65 | end 66 | end 67 | 68 | test "should redirect if confirmation link is incorrect" do 69 | get edit_confirmation_path("not_a_real_token") 70 | assert_redirected_to new_confirmation_path 71 | assert_not_nil flash[:alert] 72 | end 73 | 74 | test "should resend confirmation email if user is unconfirmed" do 75 | assert_emails 1 do 76 | post confirmations_path, params: {user: {email: @unconfirmed_user.email}} 77 | end 78 | 79 | assert_redirected_to root_path 80 | assert_not_nil flash[:notice] 81 | end 82 | 83 | test "should prevent user from confirming if they are already confirmed" do 84 | assert_no_emails do 85 | post confirmations_path, params: {user: {email: @confirmed_user.email}} 86 | end 87 | assert_redirected_to new_confirmation_path 88 | assert_not_nil flash[:alert] 89 | end 90 | 91 | test "should get new if not authenticated" do 92 | get new_confirmation_path 93 | assert_response :ok 94 | end 95 | 96 | test "should prevent authenticated user from confirming" do 97 | freeze_time do 98 | confirmation_token = @confirmed_user.generate_confirmation_token 99 | 100 | login @confirmed_user 101 | 102 | get edit_confirmation_path(confirmation_token) 103 | 104 | assert_not_equal Time.current, @confirmed_user.reload.confirmed_at 105 | assert_redirected_to new_confirmation_path 106 | assert_not_nil flash[:alert] 107 | end 108 | end 109 | 110 | test "should not prevent authenticated user confirming their unconfirmed_email" do 111 | unconfirmed_email = @reconfirmed_user.unconfirmed_email 112 | 113 | freeze_time do 114 | login @reconfirmed_user 115 | 116 | confirmation_token = @reconfirmed_user.generate_confirmation_token 117 | 118 | get edit_confirmation_path(confirmation_token) 119 | 120 | assert_equal Time.current, @reconfirmed_user.reload.confirmed_at 121 | assert @reconfirmed_user.reload.confirmed? 122 | assert_equal unconfirmed_email, @reconfirmed_user.reload.email 123 | assert_nil @reconfirmed_user.reload.unconfirmed_email 124 | assert_redirected_to root_path 125 | assert_not_nil flash[:notice] 126 | end 127 | end 128 | 129 | test "should prevent authenticated user from submitting the confirmation form" do 130 | login @confirmed_user 131 | 132 | get new_confirmation_path 133 | assert_redirected_to root_path 134 | assert_not_nil flash[:alert] 135 | 136 | assert_no_emails do 137 | post confirmations_path, params: {user: {email: @confirmed_user.email}} 138 | end 139 | 140 | assert_redirected_to root_path 141 | assert_not_nil flash[:alert] 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/test/controllers/passwords_controller_test.rb.tt: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class PasswordsControllerTest < ActionDispatch::IntegrationTest 4 | setup do 5 | @confirmed_user = User.create!(email: "confirmed_user@example.com", password: "password", password_confirmation: "password", confirmed_at: 1.week.ago) 6 | end 7 | 8 | test "should get edit" do 9 | password_reset_token = @confirmed_user.generate_password_reset_token 10 | 11 | get edit_password_path(password_reset_token) 12 | assert_response :ok 13 | end 14 | 15 | test "should redirect from edit if password link expired" do 16 | password_reset_token = @confirmed_user.generate_password_reset_token 17 | 18 | travel_to 601.seconds.from_now 19 | get edit_password_path(password_reset_token) 20 | 21 | assert_redirected_to new_password_path 22 | assert_not_nil flash[:alert] 23 | end 24 | 25 | test "should redirect from edit if password link is incorrect" do 26 | get edit_password_path("not_a_real_token") 27 | 28 | assert_redirected_to new_password_path 29 | assert_not_nil flash[:alert] 30 | end 31 | 32 | test "should redirect from edit if user is not confirmed" do 33 | @confirmed_user.update!(confirmed_at: nil) 34 | password_reset_token = @confirmed_user.generate_password_reset_token 35 | 36 | get edit_password_path(password_reset_token) 37 | 38 | assert_redirected_to new_confirmation_path 39 | assert_not_nil flash[:alert] 40 | end 41 | 42 | test "should redirect from edit if user is authenticated" do 43 | password_reset_token = @confirmed_user.generate_password_reset_token 44 | 45 | login @confirmed_user 46 | 47 | get edit_password_path(password_reset_token) 48 | assert_redirected_to root_path 49 | end 50 | 51 | test "should get new" do 52 | get new_password_path 53 | assert_response :ok 54 | end 55 | 56 | test "should redirect from new if user is authenticated" do 57 | login @confirmed_user 58 | 59 | get new_password_path 60 | assert_redirected_to root_path 61 | end 62 | 63 | test "should send password reset mailer" do 64 | assert_emails 1 do 65 | post passwords_path, params: { 66 | user: { 67 | email: @confirmed_user.email.upcase 68 | } 69 | } 70 | end 71 | 72 | assert_redirected_to root_path 73 | assert_not_nil flash[:notice] 74 | end 75 | 76 | test "should update password" do 77 | password_reset_token = @confirmed_user.generate_password_reset_token 78 | 79 | put password_path(password_reset_token), params: { 80 | user: { 81 | password: "password", 82 | password_confirmation: "password" 83 | } 84 | } 85 | 86 | assert_redirected_to login_path 87 | assert_not_nil flash[:notice] 88 | end 89 | 90 | test "should handle errors" do 91 | password_reset_token = @confirmed_user.generate_password_reset_token 92 | 93 | put password_path(password_reset_token), params: { 94 | user: { 95 | password: "password", 96 | password_confirmation: "password_that_does_not_match" 97 | } 98 | } 99 | 100 | assert_not_nil flash[:alert] 101 | end 102 | 103 | test "should not update password if authenticated" do 104 | password_reset_token = @confirmed_user.generate_password_reset_token 105 | 106 | login @confirmed_user 107 | 108 | put password_path(password_reset_token), params: { 109 | user: { 110 | password: "password", 111 | password_confirmation: "password" 112 | 113 | } 114 | } 115 | 116 | get new_password_path 117 | assert_redirected_to root_path 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/test/controllers/sessions_controller_test.rb.tt: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class SessionsControllerTest < ActionDispatch::IntegrationTest 4 | setup do 5 | @unconfirmed_user = User.create!(email: "unconfirmed_user@example.com", password: "password", password_confirmation: "password") 6 | @confirmed_user = User.create!(email: "confirmed_user@example.com", password: "password", password_confirmation: "password", confirmed_at: Time.current) 7 | end 8 | 9 | test "should get login if anonymous" do 10 | get login_path 11 | assert_response :ok 12 | end 13 | 14 | test "should redirect from login if authenticated" do 15 | login @confirmed_user 16 | 17 | get login_path 18 | assert_redirected_to root_path 19 | end 20 | 21 | test "should login and create active session if confirmed" do 22 | assert_difference("@confirmed_user.active_sessions.count") do 23 | post login_path, params: { 24 | user: { 25 | email: @confirmed_user.email, 26 | password: @confirmed_user.password 27 | } 28 | } 29 | end 30 | assert_redirected_to root_path 31 | assert_equal @confirmed_user, current_user 32 | end 33 | 34 | test "should remember user when logging in" do 35 | assert_nil cookies[:remember_token] 36 | 37 | post login_path, params: { 38 | user: { 39 | email: @confirmed_user.email, 40 | password: @confirmed_user.password, 41 | remember_me: 1 42 | } 43 | } 44 | 45 | assert_not_nil current_user 46 | assert_not_nil cookies[:remember_token] 47 | end 48 | 49 | test "should forget user when logging out" do 50 | login @confirmed_user, remember_user: true 51 | 52 | delete logout_path 53 | 54 | # FIXME: Expected "" to be nil. 55 | # When I run byebug in SessionsController#destroy cookies[:remember_token] does == nil. 56 | # I think this might be a bug in Rails? 57 | # assert_nil cookies[:remember_token] 58 | assert cookies[:remember_token].blank? 59 | assert_nil current_user 60 | assert_redirected_to root_path 61 | assert_not_nil flash[:notice] 62 | end 63 | 64 | test "should not login if unconfirmed" do 65 | post login_path, params: { 66 | user: { 67 | email: @unconfirmed_user.email, 68 | password: @unconfirmed_user.password 69 | } 70 | } 71 | assert_equal "Incorrect email or password.", flash[:alert] 72 | assert_nil current_user 73 | assert_redirected_to new_confirmation_path 74 | end 75 | 76 | test "should handle invalid login" do 77 | post login_path, params: { 78 | user: { 79 | email: @confirmed_user.email, 80 | password: "foo" 81 | } 82 | } 83 | assert_not_nil flash[:alert] 84 | assert_nil current_user 85 | end 86 | 87 | test "should logout and delete current active session if authenticated" do 88 | login @confirmed_user 89 | 90 | assert_difference("@confirmed_user.active_sessions.count", -1) do 91 | delete logout_path 92 | end 93 | 94 | assert_nil current_user 95 | assert_redirected_to root_path 96 | assert_not_nil flash[:notice] 97 | end 98 | 99 | test "should not logout if anonymous" do 100 | login @confirmed_user 101 | 102 | delete logout_path 103 | assert_redirected_to root_path 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/test/controllers/users_controller_test.rb.tt: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class UsersControllerTest < ActionDispatch::IntegrationTest 4 | setup do 5 | @confirmed_user = User.create!(email: "confirmed_user@example.com", password: "password", password_confirmation: "password", confirmed_at: Time.current) 6 | end 7 | 8 | test "should load sign up page for anonymous users" do 9 | get sign_up_path 10 | assert_response :ok 11 | end 12 | 13 | test "should redirect authenticated users from signing up" do 14 | login @confirmed_user 15 | 16 | get sign_up_path 17 | assert_redirected_to root_path 18 | 19 | assert_no_difference("User.count") do 20 | post sign_up_path, params: { 21 | user: { 22 | email: "some_unique_email@example.com", 23 | password: "password", 24 | password_confirmation: "password" 25 | } 26 | } 27 | end 28 | end 29 | 30 | test "should create user and send confirmation instructions" do 31 | assert_difference("User.count", 1) do 32 | assert_emails 1 do 33 | post sign_up_path, params: { 34 | user: { 35 | email: "some_unique_email@example.com", 36 | password: "password", 37 | password_confirmation: "password" 38 | } 39 | } 40 | end 41 | end 42 | 43 | assert_redirected_to root_path 44 | assert_not_nil flash[:notice] 45 | end 46 | 47 | test "should handle errors when signing up" do 48 | assert_no_difference("User.count") do 49 | assert_no_emails do 50 | post sign_up_path, params: { 51 | user: { 52 | email: "some_unique_email@example.com", 53 | password: "password", 54 | password_confirmation: "wrong_password" 55 | } 56 | } 57 | end 58 | end 59 | end 60 | 61 | test "should get edit if authorized" do 62 | login(@confirmed_user) 63 | 64 | get account_path 65 | assert_response :ok 66 | end 67 | 68 | test "should redirect unauthorized user from editing account" do 69 | get account_path 70 | assert_redirected_to login_path 71 | assert_not_nil flash[:alert] 72 | end 73 | 74 | test "should edit email" do 75 | unconfirmed_email = "unconfirmed_user@example.com" 76 | current_email = @confirmed_user.email 77 | 78 | login(@confirmed_user) 79 | 80 | assert_emails 1 do 81 | put account_path, params: { 82 | user: { 83 | unconfirmed_email: unconfirmed_email, 84 | current_password: "password" 85 | } 86 | } 87 | end 88 | 89 | assert_not_nil flash[:notice] 90 | assert_equal current_email, @confirmed_user.reload.email 91 | end 92 | 93 | test "should not edit email if current_password is incorrect" do 94 | unconfirmed_email = "unconfirmed_user@example.com" 95 | current_email = @confirmed_user.email 96 | 97 | login(@confirmed_user) 98 | 99 | assert_no_emails do 100 | put account_path, params: { 101 | user: { 102 | unconfirmed_email: unconfirmed_email, 103 | current_password: "wrong_password" 104 | } 105 | } 106 | end 107 | 108 | assert_not_nil flash[:notice] 109 | assert_equal current_email, @confirmed_user.reload.email 110 | end 111 | 112 | test "should update password" do 113 | login(@confirmed_user) 114 | 115 | put account_path, params: { 116 | user: { 117 | current_password: "password", 118 | password: "new_password", 119 | password_confirmation: "new_password" 120 | } 121 | } 122 | 123 | assert_redirected_to root_path 124 | assert_not_nil flash[:notice] 125 | end 126 | 127 | test "should not update password if current_password is incorrect" do 128 | login(@confirmed_user) 129 | 130 | put account_path, params: { 131 | user: { 132 | current_password: "wrong_password", 133 | password: "new_password", 134 | password_confirmation: "new_password" 135 | } 136 | } 137 | 138 | assert_response :unprocessable_entity 139 | end 140 | 141 | test "should delete user" do 142 | login(@confirmed_user) 143 | 144 | delete account_path(@confirmed_user) 145 | 146 | assert_nil current_user 147 | assert_redirected_to root_path 148 | assert_not_nil flash[:notice] 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/test/integration/friendly_redirects_test.rb.tt: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class FriendlyRedirectsTest < ActionDispatch::IntegrationTest 4 | setup do 5 | @confirmed_user = User.create!(email: "confirmed_user@example.com", password: "password", password_confirmation: "password", confirmed_at: Time.current) 6 | end 7 | 8 | test "redirect to requested url after sign in" do 9 | get account_path 10 | 11 | assert_redirected_to login_path 12 | login(@confirmed_user) 13 | 14 | assert_redirected_to account_path 15 | end 16 | 17 | test "redirects to root path after sign in" do 18 | get login_path 19 | login(@confirmed_user) 20 | 21 | assert_redirected_to root_path 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/test/integration/user_interface_test.rb.tt: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class UserInterfaceTest < ActionDispatch::IntegrationTest 4 | setup do 5 | @confirmed_user = User.create!(email: "confirmed_user@example.com", password: "password", password_confirmation: "password", confirmed_at: Time.current) 6 | end 7 | 8 | test "should render active sessions on account page" do 9 | login @confirmed_user 10 | @confirmed_user.active_sessions.last.update!(user_agent: "Mozilla", ip_address: "123.457.789") 11 | 12 | get account_path 13 | 14 | assert_match "Mozilla", @response.body 15 | assert_match "123.457.789", @response.body 16 | end 17 | 18 | test "should render buttons to delete specific active sessions" do 19 | login @confirmed_user 20 | 21 | get account_path 22 | 23 | assert_select "button", "Log out of all other sessions" 24 | assert_match destroy_all_active_sessions_path, @response.body 25 | 26 | assert_select "table" do 27 | assert_select "button", "Sign Out" 28 | end 29 | assert_match active_session_path(@confirmed_user.active_sessions.last), @response.body 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/test/mailers/previews/user_mailer_preview.rb.tt: -------------------------------------------------------------------------------- 1 | # Preview all emails at http://localhost:3000/rails/mailers/user_mailer 2 | class UserMailerPreview < ActionMailer::Preview 3 | # Preview this email at http://localhost:3000/rails/mailers/user_mailer/confirmation 4 | def confirmation 5 | @unconfirmed_user = User.find_by(email: "unconfirmed_user@example.com") || User.create!(email: "unconfirmed_user@example.com", password: "password", password_confirmation: "password") 6 | @unconfirmed_user.update!(confirmed_at: nil) 7 | confirmation_token = @unconfirmed_user.generate_confirmation_token 8 | UserMailer.confirmation(@unconfirmed_user, confirmation_token) 9 | end 10 | 11 | # Preview this email at http://localhost:3000/rails/mailers/user_mailer/password_reset 12 | def password_reset 13 | @password_reset_user = User.find_by(email: "password_reset_user@example.com") || User.create!(email: "password_reset_user@example.com", password: "password", password_confirmation: "password", confirmed_at: Time.current) 14 | password_reset_token = @password_reset_user.generate_password_reset_token 15 | UserMailer.password_reset(@password_reset_user, password_reset_token) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/test/mailers/user_mailer_test.rb.tt: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class UserMailerTest < ActionMailer::TestCase 4 | setup do 5 | @user = User.create!(email: "some_unique_email@example.com", password: "password", password_confirmation: "password") 6 | end 7 | 8 | test "confirmation" do 9 | confirmation_token = @user.generate_confirmation_token 10 | mail = UserMailer.confirmation(@user, confirmation_token) 11 | assert_equal "Confirmation Instructions", mail.subject 12 | assert_equal [@user.email], mail.to 13 | assert_equal [User::MAILER_FROM_EMAIL], mail.from 14 | assert_match confirmation_token, mail.body.encoded 15 | end 16 | 17 | test "password_reset" do 18 | password_reset_token = @user.generate_password_reset_token 19 | mail = UserMailer.password_reset(@user, password_reset_token) 20 | assert_equal "Password Reset Instructions", mail.subject 21 | assert_equal [@user.email], mail.to 22 | assert_equal [User::MAILER_FROM_EMAIL], mail.from 23 | assert_match password_reset_token, mail.body.encoded 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/test/models/active_session_test.rb.tt: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ActiveSessionTest < ActiveSupport::TestCase 4 | setup do 5 | @user = User.new(email: "unique_email@example.com", password: "password", password_confirmation: "password") 6 | @active_session = @user.active_sessions.build 7 | end 8 | 9 | test "should be valid" do 10 | assert @active_session.valid? 11 | end 12 | 13 | test "should have a user" do 14 | @active_session.user = nil 15 | 16 | assert_not @active_session.valid? 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/test/models/user_test.rb.tt: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | include ActionMailer::TestHelper 5 | 6 | setup do 7 | @user = User.new(email: "unique_email@example.com", password: "password", password_confirmation: "password") 8 | end 9 | 10 | test "should be valid" do 11 | assert @user.valid? 12 | end 13 | 14 | test "should have email" do 15 | @user.email = nil 16 | assert_not @user.valid? 17 | end 18 | 19 | test "email should be unique" do 20 | @user.save! 21 | @invalid_user = User.new(email: @user.email) 22 | 23 | assert_not @invalid_user.valid? 24 | end 25 | 26 | test "email should be saved as lowercase" do 27 | email = "unique_email@example.com" 28 | 29 | @user = User.new(email: email.upcase, password: "password", password_confirmation: "password") 30 | @user.save! 31 | 32 | assert_equal email.downcase, @user.email 33 | end 34 | 35 | test "email should be valid" do 36 | invalid_emails = %w[foo foo@ foo@bar.] 37 | 38 | invalid_emails.each do |invalid_email| 39 | @user.email = invalid_email 40 | assert_not @user.valid? 41 | end 42 | end 43 | 44 | test "should respond to confirmed?" do 45 | assert_not @user.confirmed? 46 | 47 | @user.confirmed_at = Time.now 48 | 49 | assert @user.confirmed? 50 | end 51 | 52 | test "should respond to unconfirmed?" do 53 | assert @user.unconfirmed? 54 | 55 | @user.confirmed_at = Time.now 56 | 57 | assert_not @user.unconfirmed? 58 | end 59 | 60 | test "should respond to reconfirming?" do 61 | assert_not @user.reconfirming? 62 | 63 | @user.unconfirmed_email = "unconfirmed_email@example.com" 64 | 65 | assert @user.reconfirming? 66 | end 67 | 68 | test "should respond to unconfirmed_or_reconfirming?" do 69 | assert @user.unconfirmed_or_reconfirming? 70 | 71 | @user.unconfirmed_email = "unconfirmed_email@example.com" 72 | @user.confirmed_at = Time.now 73 | 74 | assert @user.unconfirmed_or_reconfirming? 75 | end 76 | 77 | test "should send confirmation email" do 78 | @user.save! 79 | 80 | assert_emails 1 do 81 | @user.send_confirmation_email! 82 | end 83 | 84 | assert_equal @user.email, ActionMailer::Base.deliveries.last.to[0] 85 | end 86 | 87 | test "should send confirmation email to unconfirmed_email" do 88 | @user.save! 89 | @user.update!(unconfirmed_email: "unconfirmed_email@example.com") 90 | 91 | assert_emails 1 do 92 | @user.send_confirmation_email! 93 | end 94 | 95 | assert_equal @user.unconfirmed_email, ActionMailer::Base.deliveries.last.to[0] 96 | end 97 | 98 | test "should respond to send_password_reset_email!" do 99 | @user.save! 100 | 101 | assert_emails 1 do 102 | @user.send_password_reset_email! 103 | end 104 | end 105 | 106 | test "should downcase unconfirmed_email" do 107 | email = "UNCONFIRMED_EMAIL@EXAMPLE.COM" 108 | @user.unconfirmed_email = email 109 | @user.save! 110 | 111 | assert_equal email.downcase, @user.unconfirmed_email 112 | end 113 | 114 | test "unconfirmed_email should be valid" do 115 | invalid_emails = %w[foo foo@ foo@bar.] 116 | 117 | invalid_emails.each do |invalid_email| 118 | @user.unconfirmed_email = invalid_email 119 | assert_not @user.valid? 120 | end 121 | end 122 | 123 | test "unconfirmed_email does not need to be available" do 124 | @user.save! 125 | @user.unconfirmed_email = @user.email 126 | assert @user.valid? 127 | end 128 | 129 | test ".confirm! should return false if already confirmed" do 130 | @confirmed_user = User.new(email: "unique_email@example.com", password: "password", password_confirmation: "password", confirmed_at: Time.current) 131 | 132 | assert_not @confirmed_user.confirm! 133 | end 134 | 135 | test ".confirm! should update email if reconfirming" do 136 | @reconfirmed_user = User.new(email: "unique_email@example.com", password: "password", password_confirmation: "password", confirmed_at: 1.week.ago, unconfirmed_email: "unconfirmed_email@example.com") 137 | new_email = @reconfirmed_user.unconfirmed_email 138 | 139 | freeze_time do 140 | @reconfirmed_user.confirm! 141 | 142 | assert_equal new_email, @reconfirmed_user.reload.email 143 | assert_nil @reconfirmed_user.reload.unconfirmed_email 144 | assert_equal Time.current, @reconfirmed_user.reload.confirmed_at 145 | end 146 | end 147 | 148 | test ".confirm! should not update email if already taken" do 149 | @confirmed_user = User.create!(email: "user1@example.com", password: "password", password_confirmation: "password") 150 | @reconfirmed_user = User.create!(email: "user2@example.com", password: "password", password_confirmation: "password", confirmed_at: 1.week.ago, unconfirmed_email: @confirmed_user.email) 151 | 152 | freeze_time do 153 | assert_not @reconfirmed_user.confirm! 154 | end 155 | end 156 | 157 | test ".confirm! should set confirmed_at" do 158 | @unconfirmed_user = User.create!(email: "unique_email@example.com", password: "password", password_confirmation: "password") 159 | 160 | freeze_time do 161 | @unconfirmed_user.confirm! 162 | 163 | assert_equal Time.current, @unconfirmed_user.reload.confirmed_at 164 | end 165 | end 166 | 167 | test "should create active session" do 168 | @user.save! 169 | 170 | assert_difference("@user.active_sessions.count", 1) do 171 | @user.active_sessions.create! 172 | end 173 | end 174 | 175 | test "should destroy associated active session when destryoed" do 176 | @user.save! 177 | @user.active_sessions.create! 178 | 179 | assert_difference("@user.active_sessions.count", -1) do 180 | @user.destroy! 181 | end 182 | end 183 | end 184 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/test/system/logins_test.rb.tt: -------------------------------------------------------------------------------- 1 | require "application_system_test_case" 2 | 3 | class LoginsTest < ApplicationSystemTestCase 4 | setup do 5 | @confirmed_user = User.create!(email: "confirmed_user@example.com", password: "password", password_confirmation: "password", confirmed_at: Time.current) 6 | end 7 | 8 | test "should login and create active session if confirmed" do 9 | visit login_path 10 | 11 | fill_in "Email", with: @confirmed_user.email 12 | fill_in "Password", with: @confirmed_user.password 13 | click_on "Sign In" 14 | 15 | sleep 0.1 16 | 17 | assert_not_nil @confirmed_user.active_sessions.last.user_agent 18 | assert_not_nil @confirmed_user.active_sessions.last.ip_address 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/user.rb.tt: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | CONFIRMATION_TOKEN_EXPIRATION = 10.minutes 3 | MAILER_FROM_EMAIL = "no-reply@example.com" 4 | PASSWORD_RESET_TOKEN_EXPIRATION = 10.minutes 5 | 6 | attr_accessor :current_password 7 | 8 | has_secure_password 9 | 10 | has_many :active_sessions, dependent: :destroy 11 | 12 | before_save :downcase_email 13 | before_save :downcase_unconfirmed_email 14 | 15 | validates :email, format: {with: URI::MailTo::EMAIL_REGEXP}, presence: true, uniqueness: true 16 | validates :unconfirmed_email, format: {with: URI::MailTo::EMAIL_REGEXP, allow_blank: true} 17 | 18 | def self.authenticate_by(attributes) 19 | passwords, identifiers = attributes.to_h.partition do |name, value| 20 | !has_attribute?(name) && has_attribute?("#{name}_digest") 21 | end.map(&:to_h) 22 | 23 | raise ArgumentError, "One or more password arguments are required" if passwords.empty? 24 | raise ArgumentError, "One or more finder arguments are required" if identifiers.empty? 25 | if (record = find_by(identifiers)) 26 | record if passwords.count { |name, value| record.public_send(:"authenticate_#{name}", value) } == passwords.size 27 | else 28 | new(passwords) 29 | nil 30 | end 31 | end 32 | 33 | def confirm! 34 | if unconfirmed_or_reconfirming? 35 | if unconfirmed_email.present? 36 | return false unless update(email: unconfirmed_email, unconfirmed_email: nil) 37 | end 38 | update_columns(confirmed_at: Time.current) 39 | else 40 | false 41 | end 42 | end 43 | 44 | def confirmed? 45 | confirmed_at.present? 46 | end 47 | 48 | def confirmable_email 49 | if unconfirmed_email.present? 50 | unconfirmed_email 51 | else 52 | email 53 | end 54 | end 55 | 56 | def generate_confirmation_token 57 | signed_id expires_in: CONFIRMATION_TOKEN_EXPIRATION, purpose: :confirm_email 58 | end 59 | 60 | def generate_password_reset_token 61 | signed_id expires_in: PASSWORD_RESET_TOKEN_EXPIRATION, purpose: :reset_password 62 | end 63 | 64 | def send_confirmation_email! 65 | confirmation_token = generate_confirmation_token 66 | UserMailer.confirmation(self, confirmation_token).deliver_now 67 | end 68 | 69 | def send_password_reset_email! 70 | password_reset_token = generate_password_reset_token 71 | UserMailer.password_reset(self, password_reset_token).deliver_now 72 | end 73 | 74 | def reconfirming? 75 | unconfirmed_email.present? 76 | end 77 | 78 | def unconfirmed? 79 | !confirmed? 80 | end 81 | 82 | def unconfirmed_or_reconfirming? 83 | unconfirmed? || reconfirming? 84 | end 85 | 86 | private 87 | 88 | def downcase_email 89 | self.email = email.downcase 90 | end 91 | 92 | def downcase_unconfirmed_email 93 | return if unconfirmed_email.nil? 94 | self.unconfirmed_email = unconfirmed_email.downcase 95 | end 96 | end -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/user_mailer.rb.tt: -------------------------------------------------------------------------------- 1 | class UserMailer < ApplicationMailer 2 | default from: User::MAILER_FROM_EMAIL 3 | 4 | # Subject can be set in your I18n file at config/locales/en.yml 5 | # with the following lookup: 6 | # 7 | # en.user_mailer.confirmation.subject 8 | # 9 | def confirmation(user, confirmation_token) 10 | @user = user 11 | @confirmation_token = confirmation_token 12 | 13 | mail to: @user.confirmable_email, subject: "Confirmation Instructions" 14 | end 15 | 16 | def password_reset(user, password_reset_token) 17 | @user = user 18 | @password_reset_token = password_reset_token 19 | 20 | mail to: @user.email, subject: "Password Reset Instructions" 21 | end 22 | end -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/users_controller.rb.tt: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_action :authenticate_user!, only: [:edit, :destroy, :update] 3 | before_action :redirect_if_authenticated, only: [:create, :new] 4 | 5 | def create 6 | @user = User.new(create_user_params) 7 | if @user.save 8 | @user.send_confirmation_email! 9 | redirect_to root_path, notice: "Please check your email for confirmation instructions." 10 | else 11 | render :new, status: :unprocessable_entity 12 | end 13 | end 14 | 15 | def destroy 16 | current_user.destroy 17 | reset_session 18 | redirect_to root_path, notice: "Your account has been deleted." 19 | end 20 | 21 | def edit 22 | @user = current_user 23 | @active_sessions = @user.active_sessions.order(created_at: :desc) 24 | end 25 | 26 | def new 27 | @user = User.new 28 | end 29 | 30 | def update 31 | @user = current_user 32 | @active_sessions = @user.active_sessions.order(created_at: :desc) 33 | if @user.authenticate(params[:user][:current_password]) 34 | if @user.update(update_user_params) 35 | if params[:user][:unconfirmed_email].present? 36 | @user.send_confirmation_email! 37 | redirect_to root_path, notice: "Check your email for confirmation instructions." 38 | else 39 | redirect_to root_path, notice: "Account updated." 40 | end 41 | else 42 | render :edit, status: :unprocessable_entity 43 | end 44 | else 45 | flash.now[:error] = "Incorrect password" 46 | render :edit, status: :unprocessable_entity 47 | end 48 | end 49 | 50 | private 51 | 52 | def create_user_params 53 | params.require(:user).permit(:email, :password, :password_confirmation) 54 | end 55 | 56 | def update_user_params 57 | params.require(:user).permit(:current_password, :password, :password_confirmation, :unconfirmed_email) 58 | end 59 | end -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/views/confirmations/new.html.erb.tt: -------------------------------------------------------------------------------- 1 | <%%= form_with model: @user, url: confirmations_path do |form| %> 2 | <%%= form.email_field :email, required: true %> 3 | <%%= form.submit "Confirm Email" %> 4 | <%% end %> -------------------------------------------------------------------------------- /lib/generators/rails_mvp_authentication/templates/views/passwords/edit.html.erb.tt: -------------------------------------------------------------------------------- 1 | <%%= form_with url: password_path(params[:password_reset_token]), scope: :user, method: :put do |form| %> 2 |User Agent | 39 |IP Address | 40 |Signed In At | 41 |Sign Out | 42 |
---|---|---|---|
<%%= active_session.user_agent %> | 48 |<%%= active_session.ip_address %> | 49 |<%%= active_session.created_at %> | 50 |<%%= button_to "Sign Out", active_session_path(active_session), method: :delete %> | 51 |
You may have mistyped the address or the page may have moved.
63 |If you are the application owner check the logs for more information.
65 |Maybe you tried to change something you didn't have access to.
63 |If you are the application owner check the logs for more information.
65 |If you are the application owner check the logs for more information.
64 |