Explorar el Código

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 %!s(int64=7) %!d(string=hace) años
padre
commit
85cc0fd0d8

+ 55 - 1
app/controllers/dashboard_controller.rb

@@ -16,9 +16,14 @@ class DashboardController < ApplicationController
16 16
       .paginate(page: params[:nrpage], per_page: 5)
17 17
   end
18 18
 
19
-  def settings
19
+  def set_settings_params!
20 20
     @person = current_person
21 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 27
   end
23 28
 
24 29
   def update_email_settings
@@ -29,4 +34,53 @@ class DashboardController < ApplicationController
29 34
     flash_message(:success, t('settings.saved'))
30 35
     redirect_to root_path
31 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 86
 end

+ 8 - 3
app/helpers/authentication_helper.rb

@@ -33,6 +33,8 @@ module AuthenticationHelper
33 33
           value: s.id,
34 34
           httponly: true
35 35
         }
36
+      else
37
+        session[:session_id] = s.id
36 38
       end
37 39
     end
38 40
   end
@@ -58,6 +60,10 @@ module AuthenticationHelper
58 60
     # We verify that the session hasn't expired yet.
59 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 67
       return true
62 68
 
63 69
     else
@@ -92,9 +98,8 @@ module AuthenticationHelper
92 98
     if @user_session
93 99
       @user_session
94 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 103
     end
99 104
 
100 105
     # Edge case if a session no longer exists in the database

+ 7 - 0
app/models/user.rb

@@ -18,6 +18,13 @@ class User < ApplicationRecord
18 18
 
19 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 28
   private
22 29
   # Assert that the user's email address is the same as the email address of
23 30
   # the associated Person.

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

@@ -7,7 +7,6 @@
7 7
 = form_tag(update_email_settings_path, method: "post", class: "form") do
8 8
   .row
9 9
     .col-md-4
10
-      -#= hidden_field_tag(:send_attendance_reminder, "off")
11 10
       %label
12 11
         = check_box_tag(:send_attendance_reminder, 'send_attendance_reminder', @send_attendance_reminder)
13 12
         = t('settings.names.send_attendance_reminder')
@@ -15,4 +14,30 @@
15 14
       %p
16 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,6 +38,8 @@ en:
38 38
   send_email: "Send email"
39 39
   overview: "Overzicht"
40 40
 
41
+  somethingbroke: "Something broke!"
42
+
41 43
   areyousure: "Are you sure?"
42 44
 
43 45
   value_required: "A required value was omitted."

+ 2 - 0
config/locales/aardbei_nl.yml

@@ -19,6 +19,8 @@ nl:
19 19
 
20 20
   areyousure: "Weet je het zeker?"
21 21
 
22
+  somethingbroke: "Er is iets misgegaan!"
23
+
22 24
   value_required: "Een verplichte vraag was leeg."
23 25
 
24 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,6 +8,7 @@ en:
8 8
     please_sign_in: "Please sign in"
9 9
     remember_me: "Remember me"
10 10
 
11
+    current_password: "Current password"
11 12
     new_password: "New password"
12 13
     new_password_confirm: "Confirm new password"
13 14
 
@@ -17,6 +18,7 @@ en:
17 18
     invalid_token: "Invalid token!"
18 19
     expired_token: "Your token has expired, please request another one."
19 20
     invalid_user_or_pass: "Invalid username/password combination!"
21
+    invalid_pass: "Your current password is incorrect." # In settings only
20 22
     login_required: "You need to be logged in to do that."
21 23
     admin_required: "You need to be an administrator to do that."
22 24
     organizer_required: "You need to be an organizer to do that."
@@ -26,7 +28,9 @@ en:
26 28
     password_repeat_mismatch: "Password confirmation does not match your password!"
27 29
     activation_complete: "Your account has been confirmed, you may now log in."
28 30
     password_reset_complete: "Your password has been reset, you may now log in."
31
+    password_changed: "Your password has been changed!"
29 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 35
     emails:
32 36
       sent: "An email has been sent, check your inbox!"

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

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

+ 1 - 0
config/locales/settings_en.yml

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

+ 4 - 0
config/locales/settings_nl.yml

@@ -3,9 +3,13 @@ nl:
3 3
     settings: "Instellingen"
4 4
     saved: "Instellingen opgeslagen!"
5 5
     email_settings: "Emailnotificaties"
6
+    change_password: "Wachtwoord veranderen"
7
+    logout_all_sessions: "Overal afmelden"
6 8
 
7 9
     names:
8 10
       send_attendance_reminder: "Stuur een herinnering bij niet gereageerd"
9 11
 
10 12
     descriptions:
11 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,6 +4,8 @@ Rails.application.routes.draw do
4 4
   root to: 'dashboard#home'
5 5
   get 'settings', to: 'dashboard#settings', as: :settings
6 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 10
   get  'login', to: 'authentication#login_form', as: :login
9 11
   post 'login', to: 'authentication#login'

+ 10 - 0
lib/tasks/sessions.rake

@@ -0,0 +1,10 @@
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