Browse Source

Login system improvements

- Allow password change
- Allow terminating all sessions associated with an user
- Check database for non-persistent sessions as well, improving security
    (ending sessions used to take effect only later)
- Add rake tasks to clear all expired and logged-out sessions
Maarten van den Berg 7 years ago
parent
commit
85cc0fd0d8

+ 55 - 1
app/controllers/dashboard_controller.rb

16
       .paginate(page: params[:nrpage], per_page: 5)
16
       .paginate(page: params[:nrpage], per_page: 5)
17
   end
17
   end
18
 
18
 
19
-  def settings
19
+  def set_settings_params!
20
     @person = current_person
20
     @person = current_person
21
     @send_attendance_reminder = @person.send_attendance_reminder
21
     @send_attendance_reminder = @person.send_attendance_reminder
22
+    @active_sessions = Session.where(user: current_user).where(active: true).where('expires > ?', Time.now).count
23
+  end
24
+
25
+  def settings
26
+    set_settings_params!
22
   end
27
   end
23
 
28
 
24
   def update_email_settings
29
   def update_email_settings
29
     flash_message(:success, t('settings.saved'))
34
     flash_message(:success, t('settings.saved'))
30
     redirect_to root_path
35
     redirect_to root_path
31
   end
36
   end
37
+
38
+  def logout_all_sessions
39
+    u = current_user
40
+
41
+    u.logout_all_sessions!
42
+    log_out
43
+
44
+    redirect_to login_path
45
+  end
46
+
47
+  def update_password
48
+    u = current_user
49
+
50
+    current = params[:current_password]
51
+    new = params[:new_password]
52
+    confirm = params[:new_password_confirm]
53
+
54
+    if !u.authenticate(current)
55
+      flash_message(:danger, t('authentication.invalid_pass'))
56
+      redirect_to settings_path
57
+      return
58
+    end
59
+
60
+    if new.blank?
61
+      flash_message(:danger, t('authentication.password_blank'))
62
+      redirect_to settings_path
63
+      return
64
+    end
65
+
66
+    if new != confirm
67
+      flash_message(:danger, t('authentication.password_repeat_mismatch'))
68
+      redirect_to settings_path
69
+      return
70
+    end
71
+
72
+    u.password = new
73
+    u.password_confirmation = confirm
74
+    if u.save
75
+      flash_message(:success, t('authentication.password_changed'))
76
+      u.logout_all_sessions!
77
+      log_out
78
+      redirect_to login_path
79
+      return
80
+    else
81
+      flash_message(:danger, t(:somethingbroke))
82
+      Rails.logger.error('Password change failure')
83
+      redirect_to settings_path
84
+    end
85
+  end
32
 end
86
 end

+ 8 - 3
app/helpers/authentication_helper.rb

33
           value: s.id,
33
           value: s.id,
34
           httponly: true
34
           httponly: true
35
         }
35
         }
36
+      else
37
+        session[:session_id] = s.id
36
       end
38
       end
37
     end
39
     end
38
   end
40
   end
58
     # We verify that the session hasn't expired yet.
60
     # We verify that the session hasn't expired yet.
59
     if session[:user_id] && session[:expires].to_time > DateTime.now
61
     if session[:user_id] && session[:expires].to_time > DateTime.now
60
 
62
 
63
+      get_user_session
64
+
65
+      return false if !@user_session.active || @user_session.expires < Time.now
66
+
61
       return true
67
       return true
62
 
68
 
63
     else
69
     else
92
     if @user_session
98
     if @user_session
93
       @user_session
99
       @user_session
94
     else
100
     else
95
-      @user_session ||= Session.find(
96
-        cookies.signed.permanent[:session_id]
97
-      )
101
+      id = cookies.signed.permanent[:session_id] || session[:session_id]
102
+      @user_session ||= Session.find(id)
98
     end
103
     end
99
 
104
 
100
     # Edge case if a session no longer exists in the database
105
     # Edge case if a session no longer exists in the database

+ 7 - 0
app/models/user.rb

18
 
18
 
19
   before_validation :email_same_as_person
19
   before_validation :email_same_as_person
20
 
20
 
21
+  # Set all sessions associated with this User to inactive, for instance after
22
+  # a password change, or when the user selects this options in the Settings.
23
+  def logout_all_sessions!
24
+    sessions = Session.where(user: self)
25
+    sessions.update_all(active: false)
26
+  end
27
+
21
   private
28
   private
22
   # Assert that the user's email address is the same as the email address of
29
   # Assert that the user's email address is the same as the email address of
23
   # the associated Person.
30
   # the associated Person.

+ 27 - 2
app/views/dashboard/settings.html.haml

7
 = form_tag(update_email_settings_path, method: "post", class: "form") do
7
 = form_tag(update_email_settings_path, method: "post", class: "form") do
8
   .row
8
   .row
9
     .col-md-4
9
     .col-md-4
10
-      -#= hidden_field_tag(:send_attendance_reminder, "off")
11
       %label
10
       %label
12
         = check_box_tag(:send_attendance_reminder, 'send_attendance_reminder', @send_attendance_reminder)
11
         = check_box_tag(:send_attendance_reminder, 'send_attendance_reminder', @send_attendance_reminder)
13
         = t('settings.names.send_attendance_reminder')
12
         = t('settings.names.send_attendance_reminder')
15
       %p
14
       %p
16
         %em= t 'settings.descriptions.send_attendance_reminder'
15
         %em= t 'settings.descriptions.send_attendance_reminder'
17
 
16
 
18
-  = submit_tag t('helpers.submit.submit', model: t('settings.settings'))
17
+  = submit_tag t('helpers.submit.submit', model: t('settings.settings')), class: 'btn btn-primary'
18
+
19
+%h2
20
+  = t 'settings.logout_all_sessions'
21
+
22
+%p
23
+  %em= t 'settings.descriptions.logout_all_sessions'
24
+
25
+%p
26
+  = t 'settings.descriptions.logged_in_at_count', count: @active_sessions
27
+
28
+= form_tag(logout_all_path, method: 'post', class: 'form') do
29
+  = submit_tag t('settings.logout_all_sessions'), class: 'btn btn-danger', data: {confirm: t(:areyousure)}
30
+%h2
31
+  = t 'settings.change_password'
32
+
33
+= form_tag(update_password_path, method: 'post', class: 'form') do
34
+  .form-group
35
+    = label_tag(:current_password, t('authentication.current_password'))
36
+    = password_field_tag(:current_password, '', class: 'form-control')
37
+  .form-group
38
+    = label_tag(:new_password, t('authentication.new_password'))
39
+    = password_field_tag(:new_password, '', class: 'form-control')
40
+  .form-group
41
+    = label_tag(:new_password_confirm, t('authentication.new_password_confirm'))
42
+    = password_field_tag(:new_password_confirm, '', class: 'form-control')
43
+  = submit_tag t('settings.change_password'), class: 'btn btn-primary'

+ 2 - 0
config/locales/aardbei_en.yml

38
   send_email: "Send email"
38
   send_email: "Send email"
39
   overview: "Overzicht"
39
   overview: "Overzicht"
40
 
40
 
41
+  somethingbroke: "Something broke!"
42
+
41
   areyousure: "Are you sure?"
43
   areyousure: "Are you sure?"
42
 
44
 
43
   value_required: "A required value was omitted."
45
   value_required: "A required value was omitted."

+ 2 - 0
config/locales/aardbei_nl.yml

19
 
19
 
20
   areyousure: "Weet je het zeker?"
20
   areyousure: "Weet je het zeker?"
21
 
21
 
22
+  somethingbroke: "Er is iets misgegaan!"
23
+
22
   value_required: "Een verplichte vraag was leeg."
24
   value_required: "Een verplichte vraag was leeg."
23
 
25
 
24
   invalid_csrf: "Ongeldig authenticiteitstoken! Als je hier terecht bent gekomen na het klikken op een link is het mogelijk dat iemand iets naars probeerde!"
26
   invalid_csrf: "Ongeldig authenticiteitstoken! Als je hier terecht bent gekomen na het klikken op een link is het mogelijk dat iemand iets naars probeerde!"

+ 4 - 0
config/locales/authentication/en.yml

8
     please_sign_in: "Please sign in"
8
     please_sign_in: "Please sign in"
9
     remember_me: "Remember me"
9
     remember_me: "Remember me"
10
 
10
 
11
+    current_password: "Current password"
11
     new_password: "New password"
12
     new_password: "New password"
12
     new_password_confirm: "Confirm new password"
13
     new_password_confirm: "Confirm new password"
13
 
14
 
17
     invalid_token: "Invalid token!"
18
     invalid_token: "Invalid token!"
18
     expired_token: "Your token has expired, please request another one."
19
     expired_token: "Your token has expired, please request another one."
19
     invalid_user_or_pass: "Invalid username/password combination!"
20
     invalid_user_or_pass: "Invalid username/password combination!"
21
+    invalid_pass: "Your current password is incorrect." # In settings only
20
     login_required: "You need to be logged in to do that."
22
     login_required: "You need to be logged in to do that."
21
     admin_required: "You need to be an administrator to do that."
23
     admin_required: "You need to be an administrator to do that."
22
     organizer_required: "You need to be an organizer to do that."
24
     organizer_required: "You need to be an organizer to do that."
26
     password_repeat_mismatch: "Password confirmation does not match your password!"
28
     password_repeat_mismatch: "Password confirmation does not match your password!"
27
     activation_complete: "Your account has been confirmed, you may now log in."
29
     activation_complete: "Your account has been confirmed, you may now log in."
28
     password_reset_complete: "Your password has been reset, you may now log in."
30
     password_reset_complete: "Your password has been reset, you may now log in."
31
+    password_changed: "Your password has been changed!"
29
     user_person_mail_mismatch: "must be the same as associated person's email"
32
     user_person_mail_mismatch: "must be the same as associated person's email"
33
+    password_blank: "Your password cannot be blank."
30
 
34
 
31
     emails:
35
     emails:
32
       sent: "An email has been sent, check your inbox!"
36
       sent: "An email has been sent, check your inbox!"

+ 4 - 0
config/locales/authentication/nl.yml

8
     please_sign_in: "Log eerst in"
8
     please_sign_in: "Log eerst in"
9
     remember_me: "Onthouden"
9
     remember_me: "Onthouden"
10
 
10
 
11
+    current_password: "Huidig wachtwoord"
11
     new_password: "Nieuw wachtwoord"
12
     new_password: "Nieuw wachtwoord"
12
     new_password_confirm: "Herhaal nieuw wachtwoord"
13
     new_password_confirm: "Herhaal nieuw wachtwoord"
13
 
14
 
16
     unknown_email: "Onbekend e-mailadres."
17
     unknown_email: "Onbekend e-mailadres."
17
     invalid_token: "Ongeldig token!"
18
     invalid_token: "Ongeldig token!"
18
     expired_token: "Je token is verlopen, vraag een nieuwe aan."
19
     expired_token: "Je token is verlopen, vraag een nieuwe aan."
20
+    invalid_pass: "Je huidige wachtwoord is onjuist."
19
     invalid_user_or_pass: "Gebruikersnaam en/of wachtwoord onjuist."
21
     invalid_user_or_pass: "Gebruikersnaam en/of wachtwoord onjuist."
20
     login_required: "Je moet eerst inloggen."
22
     login_required: "Je moet eerst inloggen."
21
     admin_required: "Je moet een beheerder zijn om dat te doen."
23
     admin_required: "Je moet een beheerder zijn om dat te doen."
26
     password_repeat_mismatch: "Je hebt niet twee keer hetzelfde wachtwoord ingevuld!"
28
     password_repeat_mismatch: "Je hebt niet twee keer hetzelfde wachtwoord ingevuld!"
27
     activation_complete: "Je account is geactiveerd, je kunt nu inloggen."
29
     activation_complete: "Je account is geactiveerd, je kunt nu inloggen."
28
     password_reset_complete: "Je wachtwoord is opnieuw ingesteld, je kunt nu inloggen."
30
     password_reset_complete: "Je wachtwoord is opnieuw ingesteld, je kunt nu inloggen."
31
+    password_changed: "Je wachtwoord is veranderd!"
29
     user_person_mail_mismatch: "moet hetzelfde zijn als het emailadres van de verbonden persoon"
32
     user_person_mail_mismatch: "moet hetzelfde zijn als het emailadres van de verbonden persoon"
33
+    password_blank: "Je wachtwoord kan niet leeg zijn."
30
 
34
 
31
     emails:
35
     emails:
32
       sent: "Mail verstuurd!"
36
       sent: "Mail verstuurd!"

+ 1 - 0
config/locales/settings_en.yml

3
     settings: "Preferences"
3
     settings: "Preferences"
4
     saved: "Preferences saved!"
4
     saved: "Preferences saved!"
5
     email_settings: "Email notifications"
5
     email_settings: "Email notifications"
6
+    change_password: "Change password"
6
 
7
 
7
     names:
8
     names:
8
       send_attendance_reminder: "Send notification on auto-respond"
9
       send_attendance_reminder: "Send notification on auto-respond"

+ 4 - 0
config/locales/settings_nl.yml

3
     settings: "Instellingen"
3
     settings: "Instellingen"
4
     saved: "Instellingen opgeslagen!"
4
     saved: "Instellingen opgeslagen!"
5
     email_settings: "Emailnotificaties"
5
     email_settings: "Emailnotificaties"
6
+    change_password: "Wachtwoord veranderen"
7
+    logout_all_sessions: "Overal afmelden"
6
 
8
 
7
     names:
9
     names:
8
       send_attendance_reminder: "Stuur een herinnering bij niet gereageerd"
10
       send_attendance_reminder: "Stuur een herinnering bij niet gereageerd"
9
 
11
 
10
     descriptions:
12
     descriptions:
11
       send_attendance_reminder: "Ontvang een email als je niet hebt gereageerd op een activiteit en je reactie automatisch op 'aanwezig' wordt gezet. Wanneer je deze herinnering ontvangt is het nog mogelijk om je reactie aan te passen."
13
       send_attendance_reminder: "Ontvang een email als je niet hebt gereageerd op een activiteit en je reactie automatisch op 'aanwezig' wordt gezet. Wanneer je deze herinnering ontvangt is het nog mogelijk om je reactie aan te passen."
14
+      logout_all_sessions: "Deze knop indrukken zorgt ervoor dat je overal je wachtwoord opnieuw moet invoeren. Dit gebeurt ook als je je wachtwoord verandert."
15
+      logged_in_at_count: "Je hebt op het moment %{count} actieve sessie(s)."

+ 2 - 0
config/routes.rb

4
   root to: 'dashboard#home'
4
   root to: 'dashboard#home'
5
   get 'settings', to: 'dashboard#settings', as: :settings
5
   get 'settings', to: 'dashboard#settings', as: :settings
6
   post 'settings', to: 'dashboard#update_email_settings', as: :update_email_settings
6
   post 'settings', to: 'dashboard#update_email_settings', as: :update_email_settings
7
+  post 'update_password', to: 'dashboard#update_password', as: :update_password
8
+  post 'logout_all', to: 'dashboard#logout_all_sessions', as: :logout_all
7
 
9
 
8
   get  'login', to: 'authentication#login_form', as: :login
10
   get  'login', to: 'authentication#login_form', as: :login
9
   post 'login', to: 'authentication#login'
11
   post 'login', to: 'authentication#login'

+ 10 - 0
lib/tasks/sessions.rake

1
+namespace :sessions do
2
+  desc "Clean all expired sessions from the database"
3
+  task :clean => :environment do
4
+    expired = Session.where('sessions.expires < ?', Time.zone.now)
5
+    deactivated = Session.where(active: false)
6
+    puts "Cleaning #{expired.count} expired, #{deactivated.count} deactivated sessions."
7
+    expired.destroy_all
8
+    deactivated.destroy_all
9
+  end
10
+end