authentication-zero 1.0.2 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +9 -7
  4. data/lib/authentication_zero/version.rb +1 -1
  5. data/lib/generators/authentication/authentication_generator.rb +20 -13
  6. data/lib/generators/authentication/templates/controllers/api/password_resets_controller.rb.tt +1 -5
  7. data/lib/generators/authentication/templates/controllers/api/sessions_controller.rb.tt +26 -4
  8. data/lib/generators/authentication/templates/controllers/html/cancellations_controller.rb.tt +1 -1
  9. data/lib/generators/authentication/templates/controllers/html/emails_controller.rb.tt +2 -2
  10. data/lib/generators/authentication/templates/controllers/html/password_resets_controller.rb.tt +3 -3
  11. data/lib/generators/authentication/templates/controllers/html/passwords_controller.rb.tt +2 -2
  12. data/lib/generators/authentication/templates/controllers/html/registrations_controller.rb.tt +1 -2
  13. data/lib/generators/authentication/templates/controllers/html/sessions_controller.rb.tt +21 -9
  14. data/lib/generators/authentication/templates/erb/cancellations/new.html.erb.tt +1 -1
  15. data/lib/generators/authentication/templates/erb/emails/edit.html.erb.tt +1 -1
  16. data/lib/generators/authentication/templates/erb/password_mailer/reset.html.erb.tt +1 -1
  17. data/lib/generators/authentication/templates/erb/password_mailer/reset.text.erb.tt +1 -1
  18. data/lib/generators/authentication/templates/erb/password_resets/edit.html.erb.tt +1 -1
  19. data/lib/generators/authentication/templates/erb/password_resets/new.html.erb.tt +1 -1
  20. data/lib/generators/authentication/templates/erb/passwords/edit.html.erb.tt +1 -1
  21. data/lib/generators/authentication/templates/erb/session_mailer/signed_in.html.erb.tt +21 -0
  22. data/lib/generators/authentication/templates/erb/session_mailer/signed_in.text.erb.tt +17 -0
  23. data/lib/generators/authentication/templates/erb/sessions/index.html.erb.tt +34 -0
  24. data/lib/generators/authentication/templates/erb/sessions/new.html.erb.tt +1 -6
  25. data/lib/generators/authentication/templates/mailers/password_mailer.rb.tt +1 -5
  26. data/lib/generators/authentication/templates/mailers/session_mailer.rb.tt +6 -0
  27. data/lib/generators/authentication/templates/migrations/create_sessions_migration.rb.tt +11 -0
  28. data/lib/generators/authentication/templates/migrations/create_table_migration.rb.tt +12 -0
  29. data/lib/generators/authentication/templates/models/current.rb.tt +5 -1
  30. data/lib/generators/authentication/templates/models/model.rb.tt +2 -28
  31. data/lib/generators/authentication/templates/models/session.rb.tt +7 -0
  32. data/lib/generators/authentication/templates/test_unit/controllers/api/cancellations_controller_test.rb.tt +3 -3
  33. data/lib/generators/authentication/templates/test_unit/controllers/api/emails_controller_test.rb.tt +4 -9
  34. data/lib/generators/authentication/templates/test_unit/controllers/api/password_resets_controller_test.rb.tt +4 -11
  35. data/lib/generators/authentication/templates/test_unit/controllers/api/passwords_controller_test.rb.tt +4 -9
  36. data/lib/generators/authentication/templates/test_unit/controllers/api/sessions_controller_test.rb.tt +16 -6
  37. data/lib/generators/authentication/templates/test_unit/controllers/html/cancellations_controller_test.rb.tt +3 -3
  38. data/lib/generators/authentication/templates/test_unit/controllers/html/emails_controller_test.rb.tt +5 -10
  39. data/lib/generators/authentication/templates/test_unit/controllers/html/password_resets_controller_test.rb.tt +8 -8
  40. data/lib/generators/authentication/templates/test_unit/controllers/html/passwords_controller_test.rb.tt +5 -10
  41. data/lib/generators/authentication/templates/test_unit/controllers/html/registrations_controller_test.rb.tt +1 -3
  42. data/lib/generators/authentication/templates/test_unit/controllers/html/sessions_controller_test.rb.tt +14 -7
  43. data/lib/generators/authentication/templates/test_unit/fixtures.yml.tt +0 -1
  44. data/lib/generators/authentication/templates/test_unit/sessions.yml.tt +6 -0
  45. data/lib/generators/authentication/templates/test_unit/system/cancellations_test.rb.tt +2 -2
  46. data/lib/generators/authentication/templates/test_unit/system/emails_test.rb.tt +2 -2
  47. data/lib/generators/authentication/templates/test_unit/system/password_resets_test.rb.tt +2 -2
  48. data/lib/generators/authentication/templates/test_unit/system/passwords_test.rb.tt +2 -2
  49. data/lib/generators/authentication/templates/test_unit/system/sessions_test.rb.tt +8 -1
  50. metadata +10 -7
  51. data/lib/generators/authentication/templates/erb/email_mailer/changed.html.erb.tt +0 -11
  52. data/lib/generators/authentication/templates/erb/email_mailer/changed.text.erb.tt +0 -9
  53. data/lib/generators/authentication/templates/erb/password_mailer/changed.html.erb.tt +0 -7
  54. data/lib/generators/authentication/templates/erb/password_mailer/changed.text.erb.tt +0 -5
  55. data/lib/generators/authentication/templates/mailers/email_mailer.rb.tt +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c038ff0229826dcb0ef7416e95f41ef7e18a01a7e5993a3995eb992c2a21a44a
4
- data.tar.gz: 6295fb855b41a0ea8242916c18c40686f41d6f32e83fe66ed8922fe3b2c24ee7
3
+ metadata.gz: 44c36959694f55d09ae33d82d3dbe2091a49372c4792eda84bfddf94c62b636c
4
+ data.tar.gz: 9d4b642209fe681865973ad1a5b13bd8bba1bf63140ae49acfe25923ce085514
5
5
  SHA512:
6
- metadata.gz: fbbbb3287c24e23260ca3ad7adcc206c864f02843c255c111b6c1f06fd822581db35c522358dfa60a626e83bb2eb0f8494274847185a122f9cd90726c94ae2cf
7
- data.tar.gz: fb427adb9595073f03b76bba0683cafd5ce9a699b25d79bbd4df2902abc7f4643f326a4b25c017ce6ab98b7e21d69d0532539aaeec8a018c3d69b7fd597f4d81
6
+ metadata.gz: cbc64b8248e0bf392983bb19a3034b76471dc54511cb7ffee7ed122bb3c7f1424ec4211b11b5d0026395d622c1e6d6b35c3be09710f5617f45db2daece00ec49
7
+ data.tar.gz: f3ccd41d8f2c417918cf65c7124fc597355d07bc96ba509bba12174d5161df33b2434a3af76a67f2e23de020f7cec549fc8a326619643373e4c05eb1b799c4bd
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- authentication-zero (1.0.2)
4
+ authentication-zero (2.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -4,26 +4,24 @@ The purpose of authentication zero is to generate a pre-built authentication sys
4
4
 
5
5
  ## Features
6
6
 
7
+ - **Simplest code ever**
7
8
  - Sign up
8
9
  - Email and password validations
9
10
  - Reset the user password and send reset instructions
10
11
  - Authentication by cookie (html)
11
12
  - Authentication by token (api)
12
- - Remember me (html)
13
- - Send e-mail when email is changed
14
- - Send e-mail when password is changed
13
+ - Send e-mail when sign-in to your account
14
+ - Manage multiple sessions
15
15
  - Cancel my account
16
16
  - Log out
17
17
 
18
18
  ## Security and best practices
19
19
 
20
20
  - [has_secure_password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password): Adds methods to set and authenticate against a BCrypt password.
21
- - [has_secure_token](https://api.rubyonrails.org/classes/ActiveRecord/SecureToken/ClassMethods.html#method-i-has_secure_token): Adds methods to generate unique tokens.
22
21
  - [signed cookies](https://api.rubyonrails.org/classes/ActionDispatch/Cookies.html): Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from the cookie again.
23
22
  - [httponly cookies](https://api.rubyonrails.org/classes/ActionDispatch/Cookies.html): A cookie with the httponly attribute is inaccessible to the JavaScript, this precaution helps mitigate cross-site scripting (XSS) attacks.
24
23
  - [signed_id](https://api.rubyonrails.org/classes/ActiveRecord/SignedId.html): Returns a signed id that is tamper proof, so it's safe to send in an email or otherwise share with the outside world.
25
24
  - [Current attributes](https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html): Abstract super class that provides a thread-isolated attributes singleton, which resets automatically before and after each request.
26
- - [Callbacks](https://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html): We use callbacks to send emails after changing an email or password.
27
25
  - [Action mailer](https://api.rubyonrails.org/classes/ActionMailer/Base.html): Action Mailer allows you to send email from your application using a mailer model and views.
28
26
  - [Log filtering](https://guides.rubyonrails.org/action_controller_overview.html#log-filtering): Parameters 'token' and 'password' are marked [FILTERED] in the log.
29
27
  - [Functional Tests](https://guides.rubyonrails.org/testing.html#functional-tests-for-your-controllers): In Rails, testing the various actions of a controller is a form of writing functional tests.
@@ -56,19 +54,23 @@ Add these lines to your `app/views/home/index.html.erb`:
56
54
 
57
55
  <p>Signed as <%= Current.user.email %></p>
58
56
 
57
+ <div>
58
+ <%= link_to "Change password", edit_passwords_path %>
59
+ </div>
60
+
59
61
  <div>
60
62
  <%= link_to "Change email", edit_emails_path %>
61
63
  </div>
62
64
 
63
65
  <div>
64
- <%= link_to "Change password", edit_passwords_path %>
66
+ <%= link_to "Manage Sessions", sessions_path %>
65
67
  </div>
66
68
 
67
69
  <div>
68
70
  <%= link_to "Cancel my account & delete my data", new_cancellations_path %>
69
71
  </div>
70
72
 
71
- <%= button_to "Log out", sign_out_path, method: :delete %>
73
+ <%= button_to "Log out", Current.session, method: :delete %>
72
74
  ```
73
75
 
74
76
  And you'll need to set up the default URL options for the mailer in each environment. Here is a possible configuration for `config/environments/development.rb`:
@@ -1,3 +1,3 @@
1
1
  module AuthenticationZero
2
- VERSION = "1.0.2"
2
+ VERSION = "2.1.1"
3
3
  end
@@ -1,6 +1,8 @@
1
1
  require "rails/generators/active_record"
2
2
 
3
3
  class AuthenticationGenerator < Rails::Generators::NamedBase
4
+ include ActiveRecord::Generators::Migration
5
+
4
6
  class_option :api, type: :boolean, desc: "Generates API authentication"
5
7
 
6
8
  class_option :migration, type: :boolean, default: true
@@ -18,14 +20,16 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
18
20
  uncomment_lines "Gemfile", /bcrypt/
19
21
  end
20
22
 
21
- def create_migration
23
+ def create_migrations
22
24
  if options.migration
23
- invoke "migration", ["create_#{table_name}", "email:string:uniq", "password:digest", "session_token:string:uniq"]
25
+ migration_template "migrations/create_table_migration.rb", "#{db_migrate_path}/create_#{table_name}.rb"
26
+ migration_template "migrations/create_sessions_migration.rb", "#{db_migrate_path}/create_sessions.rb"
24
27
  end
25
28
  end
26
29
 
27
30
  def create_models
28
31
  template "models/model.rb", "app/models/#{file_name}.rb"
32
+ template "models/session.rb", "app/models/session.rb"
29
33
  template "models/current.rb", "app/models/current.rb"
30
34
  end
31
35
 
@@ -34,6 +38,7 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
34
38
  def create_fixture_file
35
39
  if options.fixture && options.fixture_replacement.nil?
36
40
  template "#{test_framework}/fixtures.yml", "test/fixtures/#{fixture_file_name}.yml"
41
+ template "#{test_framework}/sessions.yml", "test/fixtures/sessions.yml"
37
42
  end
38
43
  end
39
44
 
@@ -45,8 +50,10 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
45
50
 
46
51
  private
47
52
  def authenticate
48
- authenticate_or_request_with_http_token do |token, _options|
49
- Current.#{singular_table_name} = #{class_name}.find_signed_session_token(token)
53
+ if session = authenticate_with_http_token { |token, _| Session.find_signed(token) }
54
+ Current.session = session
55
+ else
56
+ request_http_token_authentication
50
57
  end
51
58
  end
52
59
  CODE
@@ -56,10 +63,10 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
56
63
 
57
64
  private
58
65
  def authenticate
59
- if #{singular_table_name} = #{class_name}.find_by_session_token(cookies.signed[:session_token])
60
- Current.#{singular_table_name} = #{singular_table_name}
66
+ if session = Session.find_by_id(cookies.signed[:session_token])
67
+ Current.session = session
61
68
  else
62
- redirect_to sign_in_path, alert: "You need to sign in or sign up before continuing"
69
+ redirect_to sign_in_path
63
70
  end
64
71
  end
65
72
  CODE
@@ -74,7 +81,7 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
74
81
 
75
82
  def create_views
76
83
  if options.api
77
- directory "erb/email_mailer", "app/views/email_mailer"
84
+ directory "erb/session_mailer", "app/views/session_mailer"
78
85
  directory "erb/password_mailer", "app/views/password_mailer"
79
86
  else
80
87
  directory "#{template_engine}", "app/views"
@@ -87,11 +94,11 @@ class AuthenticationGenerator < Rails::Generators::NamedBase
87
94
 
88
95
  def add_routes
89
96
  unless options.skip_routes
90
- route "resource :password_resets, only: [:new, :edit, :create, :update]"
91
- route "resource :cancellations, only: [:new, :create]"
92
- route "resource :passwords, only: [:edit, :update]"
93
- route "resource :emails, only: [:edit, :update]"
94
- route "delete 'sign_out', to: 'sessions#destroy'"
97
+ route "resource :password_reset, only: [:new, :edit, :create, :update]"
98
+ route "resource :cancellation, only: [:new, :create]"
99
+ route "resource :password, only: [:edit, :update]"
100
+ route "resource :email, only: [:edit, :update]"
101
+ route "resources :sessions, only: [:index, :show, :destroy]"
95
102
  route "post 'sign_up', to: 'registrations#create'"
96
103
  route "get 'sign_up', to: 'registrations#new'" unless options.api?
97
104
  route "post 'sign_in', to: 'sessions#create'"
@@ -1,11 +1,7 @@
1
1
  class PasswordResetsController < ApplicationController
2
2
  skip_before_action :authenticate
3
3
 
4
- before_action :set_<%= singular_table_name %>, only: %i[ edit update ]
5
-
6
- def edit
7
- render json: { error: "Open this link in your device" }, status: :not_found
8
- end
4
+ before_action :set_<%= singular_table_name %>, only: :update
9
5
 
10
6
  def create
11
7
  if @<%= singular_table_name %> = <%= class_name %>.find_by_email(params[:email])
@@ -1,17 +1,39 @@
1
1
  class SessionsController < ApplicationController
2
- skip_before_action :authenticate, except: :destroy
2
+ skip_before_action :authenticate, only: :create
3
+
4
+ before_action :set_session, only: %i[ show destroy ]
5
+
6
+ def index
7
+ render json: Current.<%= singular_table_name %>.sessions.order(created_at: :desc)
8
+ end
9
+
10
+ def show
11
+ render json: @session
12
+ end
3
13
 
4
14
  def create
5
15
  @<%= singular_table_name %> = <%= class_name %>.find_by_email(params[:email])
6
16
 
7
17
  if @<%= singular_table_name %>.try(:authenticate, params[:password])
8
- render json: { session_token: @<%= singular_table_name %>.signed_session_token }, status: :ok
18
+ session = @<%= singular_table_name %>.sessions.create!(session_params)
19
+ response.set_header("X-Session-Token", session.signed_id)
20
+
21
+ render json: session, status: :created
9
22
  else
10
- render json: { error: "Invalid email or password" }, status: :unauthorized
23
+ render json: { error: "That email or password is incorrect" }, status: :unauthorized
11
24
  end
12
25
  end
13
26
 
14
27
  def destroy
15
- Current.<%= singular_table_name %>.regenerate_session_token
28
+ @session.destroy
16
29
  end
30
+
31
+ private
32
+ def set_session
33
+ @session = Current.<%= singular_table_name %>.sessions.find(params[:id])
34
+ end
35
+
36
+ def session_params
37
+ { user_agent: request.user_agent, ip_address: request.remote_ip }
38
+ end
17
39
  end
@@ -4,6 +4,6 @@ class CancellationsController < ApplicationController
4
4
 
5
5
  def create
6
6
  Current.<%= singular_table_name %>.destroy
7
- redirect_to sign_in_path, notice: "Bye! Your account has been successfully cancelled"
7
+ redirect_to sign_in_path, notice: "Your account is closed"
8
8
  end
9
9
  end
@@ -6,9 +6,9 @@ class EmailsController < ApplicationController
6
6
 
7
7
  def update
8
8
  if !@<%= singular_table_name %>.authenticate(params[:current_password])
9
- redirect_to edit_emails_path, alert: "The current password you entered is incorrect"
9
+ redirect_to edit_email_path, alert: "The current password you entered is incorrect"
10
10
  elsif @<%= singular_table_name %>.update(<%= "#{singular_table_name}_params" %>)
11
- redirect_to root_path, notice: "Your email has been changed successfully"
11
+ redirect_to root_path, notice: "Your email has been changed"
12
12
  else
13
13
  render :edit, status: :unprocessable_entity
14
14
  end
@@ -12,9 +12,9 @@ class PasswordResetsController < ApplicationController
12
12
  def create
13
13
  if @<%= singular_table_name %> = <%= class_name %>.find_by_email(params[:email])
14
14
  PasswordMailer.with(<%= singular_table_name %>: @<%= singular_table_name %>).reset.deliver_later
15
- redirect_to sign_in_path, notice: "You will receive an email with instructions on how to reset your password in a few minutes"
15
+ redirect_to sign_in_path, notice: "Check your email for reset instructions"
16
16
  else
17
- redirect_to new_password_resets_path, alert: "Sorry, we didn't recognize that email address"
17
+ redirect_to new_password_reset_path, alert: "Sorry, we didn't recognize that email address"
18
18
  end
19
19
  end
20
20
 
@@ -30,7 +30,7 @@ class PasswordResetsController < ApplicationController
30
30
  def set_<%= singular_table_name %>
31
31
  @<%= singular_table_name %> = <%= class_name %>.find_signed!(params[:token], purpose: :password_reset)
32
32
  rescue ActiveSupport::MessageVerifier::InvalidSignature
33
- redirect_to new_password_resets_path, alert: "Your token has expired, please request a new one"
33
+ redirect_to new_password_reset_path, alert: "Your token has expired, please request a new one"
34
34
  end
35
35
 
36
36
  def <%= "#{singular_table_name}_params" %>
@@ -6,9 +6,9 @@ class PasswordsController < ApplicationController
6
6
 
7
7
  def update
8
8
  if !@<%= singular_table_name %>.authenticate(params[:current_password])
9
- redirect_to edit_passwords_path, alert: "The current password you entered is incorrect"
9
+ redirect_to edit_password_path, alert: "The current password you entered is incorrect"
10
10
  elsif @<%= singular_table_name %>.update(<%= "#{singular_table_name}_params" %>)
11
- redirect_to root_path, notice: "Your password has been changed successfully"
11
+ redirect_to root_path, notice: "Your password has been changed"
12
12
  else
13
13
  render :edit, status: :unprocessable_entity
14
14
  end
@@ -9,8 +9,7 @@ class RegistrationsController < ApplicationController
9
9
  @<%= singular_table_name %> = <%= class_name %>.new(<%= "#{singular_table_name}_params" %>)
10
10
 
11
11
  if @<%= singular_table_name %>.save
12
- cookies.signed[:session_token] = { value: @<%= singular_table_name %>.session_token, httponly: true }
13
- redirect_to root_path, notice: "Welcome! You have signed up successfully"
12
+ redirect_to sign_in_path, notice: "Welcome! You have signed up successfully"
14
13
  else
15
14
  render :new, status: :unprocessable_entity
16
15
  end
@@ -1,5 +1,11 @@
1
1
  class SessionsController < ApplicationController
2
- skip_before_action :authenticate, except: :destroy
2
+ skip_before_action :authenticate, only: %i[ new create ]
3
+
4
+ before_action :set_session, only: :destroy
5
+
6
+ def index
7
+ @sessions = Current.<%= singular_table_name %>.sessions.order(created_at: :desc)
8
+ end
3
9
 
4
10
  def new
5
11
  @<%= singular_table_name %> = <%= class_name %>.new
@@ -9,20 +15,26 @@ class SessionsController < ApplicationController
9
15
  @<%= singular_table_name %> = <%= class_name %>.find_by_email(params[:email])
10
16
 
11
17
  if @<%= singular_table_name %>.try(:authenticate, params[:password])
12
- if params[:remember_me] == "1"
13
- cookies.signed.permanent[:session_token] = { value: @<%= singular_table_name %>.session_token, httponly: true }
14
- else
15
- cookies.signed[:session_token] = { value: @<%= singular_table_name %>.session_token, httponly: true }
16
- end
18
+ @session = @<%= singular_table_name %>.sessions.create!(session_params)
19
+ cookies.signed.permanent[:session_token] = { value: @session.id, httponly: true }
17
20
 
18
21
  redirect_to root_path, notice: "Signed in successfully"
19
22
  else
20
- redirect_to sign_in_path(email_hint: params[:email]), alert: "Invalid email or password"
23
+ redirect_to sign_in_path(email_hint: params[:email]), alert: "That email or password is incorrect"
21
24
  end
22
25
  end
23
26
 
24
27
  def destroy
25
- Current.<%= singular_table_name %>.regenerate_session_token
26
- redirect_to sign_in_path, notice: "Signed out successfully"
28
+ @session.destroy
29
+ redirect_to sessions_path, notice: "That session has been logged out"
27
30
  end
31
+
32
+ private
33
+ def set_session
34
+ @session = Current.<%= singular_table_name %>.sessions.find(params[:id])
35
+ end
36
+
37
+ def session_params
38
+ { user_agent: request.user_agent, ip_address: request.remote_ip }
39
+ end
28
40
  end
@@ -7,5 +7,5 @@
7
7
  <br>
8
8
 
9
9
  <div>
10
- <%%= button_to "OK, close my account", cancellations_path %>
10
+ <%%= button_to "OK, close my account", cancellation_path %>
11
11
  </div>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <h1>Change your email</h1>
4
4
 
5
- <%%= form_with(model: @<%= model_resource_name %>, url: emails_path) do |form| %>
5
+ <%%= form_with(model: @<%= model_resource_name %>, url: email_path) do |form| %>
6
6
  <%% if @<%= singular_table_name %>.errors.any? %>
7
7
  <div style="color: red">
8
8
  <h2><%%= pluralize(@<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p>Can't remember your password for <strong><%%= params[:<%= singular_table_name %>].email %></strong>? That's OK, it happens. Just hit the link below to set a new one.</p>
4
4
 
5
- <p><%%= link_to "Reset my password", edit_password_resets_url(token: @signed_id) %></p>
5
+ <p><%%= link_to "Reset my password", edit_password_reset_url(token: @signed_id) %></p>
6
6
 
7
7
  <p>If you did not request a password reset you can safely ignore this email, it expires in 20 minutes. Only someone with access to this email account can reset your password.</p>
8
8
 
@@ -2,7 +2,7 @@ Hey there,
2
2
 
3
3
  Can't remember your password for <%%= params[:<%= singular_table_name %>].email %>? That's OK, it happens. Just hit the link below to set a new one.
4
4
 
5
- [Reset my password]<%%= edit_password_resets_url(token: @signed_id) %>
5
+ [Reset my password]<%%= edit_password_reset_url(token: @signed_id) %>
6
6
 
7
7
  If you did not request a password reset you can safely ignore this email, it expires in 20 minutes. Only someone with access to this email account can reset your password.
8
8
 
@@ -1,6 +1,6 @@
1
1
  <h1>Reset your password</h1>
2
2
 
3
- <%%= form_with(model: @<%= model_resource_name %>, url: password_resets_path) do |form| %>
3
+ <%%= form_with(model: @<%= model_resource_name %>, url: password_reset_path) do |form| %>
4
4
  <%% if @<%= singular_table_name %>.errors.any? %>
5
5
  <div style="color: red">
6
6
  <h2><%%= pluralize(@<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <h1>Forgot your password?</h1>
4
4
 
5
- <%%= form_with(url: password_resets_path) do |form| %>
5
+ <%%= form_with(url: password_reset_path) do |form| %>
6
6
  <div>
7
7
  <%%= form.label :email, style: "display: block" %>
8
8
  <%%= form.email_field :email, autofocus: true, required: true %>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <h1>Change your password</h1>
4
4
 
5
- <%%= form_with(model: @<%= model_resource_name %>, url: passwords_path) do |form| %>
5
+ <%%= form_with(model: @<%= model_resource_name %>, url: password_path) do |form| %>
6
6
  <%% if @<%= singular_table_name %>.errors.any? %>
7
7
  <div style="color: red">
8
8
  <h2><%%= pluralize(@<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
@@ -0,0 +1,21 @@
1
+ <p>Hey there,</p>
2
+
3
+ <p>A new device just signed in to your account (<%%= @session.<%= singular_table_name %>.email %>).</p>
4
+
5
+ <p>
6
+ <strong><%%= @session.user_agent %></strong>
7
+ <br>
8
+ <%%= @session.created_at %>
9
+ <br>
10
+ IP address: <%%= @session.ip_address %>
11
+ </p>
12
+
13
+ <p><strong>If this was you, carry on.</strong> We won't notify you about sign-ins from this device again.</p>
14
+
15
+ <p><strong>If you don't recognize this device</strong>, someone else may have accessed your account. You should immediately <%%= link_to "change your password", new_password_reset_url %>.</p>
16
+
17
+ <p><strong>Tip:</strong> It's a good idea to periodically review all of the <%%= link_to "devices and sessions", sessions_url %> in your account for suspicious activity.</p>
18
+
19
+ <hr>
20
+
21
+ <p>Have questions or need help? Just reply to this email and our support team will help you sort it out.</p>
@@ -0,0 +1,17 @@
1
+ Hey there,
2
+
3
+ A new device just signed in to your account (<%%= @session.<%= singular_table_name %>.email %>).
4
+
5
+ <%%= @session.user_agent %>
6
+
7
+ <%%= @session.created_at %>
8
+
9
+ <%%= @session.ip_address %>
10
+
11
+ If this was you, carry on. We won't notify you about sign-ins from this device again.
12
+
13
+ If you don't recognize this device, someone else may have accessed your account. You should immediately [change your password]<%%= new_password_reset_url %>.
14
+
15
+ Tip: It's a good idea to periodically review all of the [devices and sessions]<%%= sessions_url %> in your account for suspicious activity.
16
+
17
+ <p>Have questions or need help? Just reply to this email and our support team will help you sort it out.
@@ -0,0 +1,34 @@
1
+ <p style="color: green"><%%= notice %></p>
2
+
3
+ <h1>Sessions</h1>
4
+
5
+ <div id="sessions">
6
+ <%% @sessions.each do |session| %>
7
+ <div id="<%%= dom_id session %>">
8
+ <p>
9
+ <strong>User Agent:</strong>
10
+ <%%= session.user_agent %>
11
+ </p>
12
+
13
+ <p>
14
+ <strong>Ip Address:</strong>
15
+ <%%= session.ip_address %>
16
+ </p>
17
+
18
+ <p>
19
+ <strong>Created at:</strong>
20
+ <%%= session.created_at %>
21
+ </p>
22
+
23
+ </div>
24
+ <p>
25
+ <%%= button_to "Log out", session, method: :delete %>
26
+ </p>
27
+ <%% end %>
28
+ </div>
29
+
30
+ <br>
31
+
32
+ <div>
33
+ <%%= link_to "Back", root_path %>
34
+ </div>
@@ -14,11 +14,6 @@
14
14
  <%%= form.password_field :password, required: true, autocomplete: "current-password" %>
15
15
  </div>
16
16
 
17
- <div>
18
- <%%= form.check_box :remember_me %>
19
- <%%= form.label :remember_me %>
20
- </div>
21
-
22
17
  <div>
23
18
  <%%= form.submit "Sign in" %>
24
19
  </div>
@@ -28,5 +23,5 @@
28
23
 
29
24
  <div>
30
25
  <%%= link_to "Sign up", sign_up_path %> |
31
- <%%= link_to "Forgot your password?", new_password_resets_path %>
26
+ <%%= link_to "Forgot your password?", new_password_reset_path %>
32
27
  </div>
@@ -1,10 +1,6 @@
1
1
  class PasswordMailer < ApplicationMailer
2
- def changed
3
- mail to: params[:<%= singular_table_name %>].email
4
- end
5
-
6
2
  def reset
7
3
  @signed_id = params[:<%= singular_table_name %>].signed_id(purpose: :password_reset, expires_in: 20.minutes)
8
- mail to: params[:<%= singular_table_name %>].email
4
+ mail to: params[:<%= singular_table_name %>].email, subject: "Reset your password"
9
5
  end
10
6
  end
@@ -0,0 +1,6 @@
1
+ class SessionMailer < ApplicationMailer
2
+ def signed_in
3
+ @session = params[:session]
4
+ mail to: @session.<%= singular_table_name %>.email, subject: "New sign-in to your account"
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :sessions do |t|
4
+ t.references :<%= singular_table_name %>, null: false, foreign_key: true
5
+ t.string :user_agent
6
+ t.string :ip_address
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= table_name %> do |t|
4
+ t.string :email, null: false
5
+ t.string :password_digest, null: false
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :<%= table_name %>, :email, unique: true
11
+ end
12
+ end
@@ -1,3 +1,7 @@
1
1
  class Current < ActiveSupport::CurrentAttributes
2
- attribute :<%= singular_table_name %>
2
+ attribute :session, :<%= singular_table_name %>
3
+
4
+ def session=(session)
5
+ super; Current.<%= singular_table_name %> = session.<%= singular_table_name %>
6
+ end
3
7
  end
@@ -1,7 +1,8 @@
1
1
  class <%= class_name %> < ApplicationRecord
2
- has_secure_token :session_token
3
2
  has_secure_password
4
3
 
4
+ has_many :sessions, dependent: :destroy
5
+
5
6
  validates :email, presence: true, uniqueness: true
6
7
  validates :email, format: { with: /\A[^@\s]+@[^@\s]+\z/ }
7
8
  validates_length_of :password, minimum: 8, allow_blank: true
@@ -9,31 +10,4 @@ class <%= class_name %> < ApplicationRecord
9
10
  before_validation do
10
11
  self.email = email.downcase.strip
11
12
  end
12
-
13
- after_update_commit do
14
- if self.email_previously_changed?
15
- EmailMailer.with(change: self.email_previous_change).changed.deliver_later
16
- end
17
- end
18
-
19
- after_update_commit do
20
- if self.password_digest_previously_changed?
21
- PasswordMailer.with(<%= singular_table_name %>: self).changed.deliver_later
22
- end
23
- end
24
- <% if options.api? %>
25
- def signed_session_token
26
- Rails.application.message_verifier(:session_token).generate(session_token)
27
- end
28
-
29
- def self.find_signed_session_token(signed_session_token)
30
- if session_token = Rails.application.message_verifier(:session_token).verified(signed_session_token)
31
- find_by_session_token(session_token)
32
- end
33
- end
34
-
35
- def as_json(options = {})
36
- super(options.merge(except: [:password_digest, :session_token]))
37
- end
38
- <% end -%>
39
13
  end
@@ -0,0 +1,7 @@
1
+ class Session < ApplicationRecord
2
+ belongs_to :<%= singular_table_name %>
3
+
4
+ after_create_commit do
5
+ SessionMailer.with(session: self).signed_in.deliver_later
6
+ end
7
+ end
@@ -7,7 +7,7 @@ class CancellationsControllerTest < ActionDispatch::IntegrationTest
7
7
 
8
8
  test "should create cancellation" do
9
9
  assert_difference("<%= class_name %>.count", -1) do
10
- post cancellations_url, headers: { "Authorization" => "Bearer #{@token}" }
10
+ post cancellation_url, headers: { "Authorization" => "Bearer #{@token}" }
11
11
  end
12
12
 
13
13
  assert_response :no_content
@@ -15,6 +15,6 @@ class CancellationsControllerTest < ActionDispatch::IntegrationTest
15
15
 
16
16
  def sign_in_as(<%= singular_table_name %>)
17
17
  post(sign_in_url, params: { email: <%= singular_table_name %>.email, password: "secret123" })
18
- [<%= singular_table_name %>, response.parsed_body["session_token"]]
19
- end
18
+ [<%= singular_table_name %>, response.headers["X-Session-Token"]]
19
+ end
20
20
  end