door_mat 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +3 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +88 -0
  6. data/Rakefile +32 -0
  7. data/app/assets/javascripts/door_mat/application.js +13 -0
  8. data/app/assets/stylesheets/door_mat/application.css +15 -0
  9. data/app/assets/stylesheets/scaffold.css +56 -0
  10. data/app/controllers/door_mat/activities_controller.rb +106 -0
  11. data/app/controllers/door_mat/application_controller.rb +14 -0
  12. data/app/controllers/door_mat/change_password_controller.rb +32 -0
  13. data/app/controllers/door_mat/forgot_passwords_controller.rb +57 -0
  14. data/app/controllers/door_mat/manage_email_controller.rb +61 -0
  15. data/app/controllers/door_mat/password_less_session_controller.rb +121 -0
  16. data/app/controllers/door_mat/reconfirm_password_controller.rb +27 -0
  17. data/app/controllers/door_mat/sessions_controller.rb +17 -0
  18. data/app/controllers/door_mat/sign_in_controller.rb +60 -0
  19. data/app/controllers/door_mat/sign_up_controller.rb +59 -0
  20. data/app/controllers/door_mat/static_controller.rb +5 -0
  21. data/app/mailers/door_mat/activity_mailer.rb +18 -0
  22. data/app/mailers/door_mat/password_less_session_mailer.rb +12 -0
  23. data/app/models/door_mat/access_token.rb +315 -0
  24. data/app/models/door_mat/activity.rb +14 -0
  25. data/app/models/door_mat/activity_confirm_email.rb +45 -0
  26. data/app/models/door_mat/activity_download_recovery_key.rb +30 -0
  27. data/app/models/door_mat/activity_reset_password.rb +47 -0
  28. data/app/models/door_mat/actor.rb +149 -0
  29. data/app/models/door_mat/change_password.rb +12 -0
  30. data/app/models/door_mat/email.rb +58 -0
  31. data/app/models/door_mat/forgot_password.rb +12 -0
  32. data/app/models/door_mat/membership.rb +42 -0
  33. data/app/models/door_mat/session.rb +315 -0
  34. data/app/models/door_mat/sign_in.rb +31 -0
  35. data/app/models/door_mat/sign_up.rb +17 -0
  36. data/app/views/door_mat/activity_mailer/confirm_email.html.erb +11 -0
  37. data/app/views/door_mat/activity_mailer/confirm_email.text.erb +7 -0
  38. data/app/views/door_mat/activity_mailer/reset_password.html.erb +11 -0
  39. data/app/views/door_mat/activity_mailer/reset_password.text.erb +7 -0
  40. data/app/views/door_mat/change_password/new.html.erb +22 -0
  41. data/app/views/door_mat/forgot_passwords/choose_new_password.html.erb +34 -0
  42. data/app/views/door_mat/forgot_passwords/new.html.erb +14 -0
  43. data/app/views/door_mat/helpers/_errors_if_any.html.erb +10 -0
  44. data/app/views/door_mat/manage_email/new.html.erb +14 -0
  45. data/app/views/door_mat/password_less_session/access_token.html.erb +16 -0
  46. data/app/views/door_mat/password_less_session/new.html.erb +34 -0
  47. data/app/views/door_mat/password_less_session_mailer/send_token.html.erb +11 -0
  48. data/app/views/door_mat/password_less_session_mailer/send_token.text.erb +7 -0
  49. data/app/views/door_mat/reconfirm_password/new.html.erb +12 -0
  50. data/app/views/door_mat/sign_in/new.html.erb +30 -0
  51. data/app/views/door_mat/sign_up/new.html.erb +24 -0
  52. data/app/views/door_mat/static/add_email_success.html.erb +5 -0
  53. data/app/views/door_mat/static/change_password_success.html.erb +2 -0
  54. data/app/views/door_mat/static/confirm_email_success.html.erb +2 -0
  55. data/app/views/door_mat/static/email_confirmation_required.html.erb +17 -0
  56. data/app/views/door_mat/static/forgot_password_verification_mail_sent.html.erb +2 -0
  57. data/app/views/door_mat/static/reconfirm_password_success.html.erb +4 -0
  58. data/app/views/door_mat/static/sign_in_success.html.erb +5 -0
  59. data/app/views/door_mat/static/sign_out_success.html.erb +5 -0
  60. data/app/views/door_mat/static/sign_up_success.html.erb +4 -0
  61. data/bin/rails +12 -0
  62. data/config/locales/en.yml +73 -0
  63. data/config/routes.rb +48 -0
  64. data/db/migrate/20140616234935_create_door_mat_actors.rb +23 -0
  65. data/db/migrate/20140617233357_create_door_mat_sessions.rb +17 -0
  66. data/db/migrate/20140630043202_create_door_mat_emails.rb +12 -0
  67. data/db/migrate/20140702045729_create_door_mat_activities.rb +14 -0
  68. data/db/migrate/20141115183045_create_door_mat_access_tokens.rb +17 -0
  69. data/db/migrate/20141121191824_create_door_mat_memberships.rb +14 -0
  70. data/db/migrate/20150910182126_rename_session_guid_column.rb +5 -0
  71. data/db/migrate/20150918210831_add_access_token_rating_column.rb +5 -0
  72. data/door_mat.gemspec +37 -0
  73. data/lib/door_mat.rb +20 -0
  74. data/lib/door_mat/attr_asymmetric_store.rb +82 -0
  75. data/lib/door_mat/attr_symmetric_store.rb +82 -0
  76. data/lib/door_mat/configuration.rb +193 -0
  77. data/lib/door_mat/controller.rb +117 -0
  78. data/lib/door_mat/crypto.rb +49 -0
  79. data/lib/door_mat/crypto/asymmetric_store.rb +77 -0
  80. data/lib/door_mat/crypto/fast_hash.rb +17 -0
  81. data/lib/door_mat/crypto/password_hash.rb +39 -0
  82. data/lib/door_mat/crypto/secure_compare.rb +23 -0
  83. data/lib/door_mat/crypto/symmetric_store.rb +68 -0
  84. data/lib/door_mat/engine.rb +23 -0
  85. data/lib/door_mat/process/actor_password_change.rb +65 -0
  86. data/lib/door_mat/process/actor_sign_in.rb +38 -0
  87. data/lib/door_mat/process/actor_sign_up.rb +39 -0
  88. data/lib/door_mat/process/create_new_anonymous_actor.rb +36 -0
  89. data/lib/door_mat/process/manage_email.rb +42 -0
  90. data/lib/door_mat/process/reset_password.rb +50 -0
  91. data/lib/door_mat/regex.rb +17 -0
  92. data/lib/door_mat/test_helper.rb +58 -0
  93. data/lib/door_mat/url_protocol.rb +9 -0
  94. data/lib/door_mat/version.rb +3 -0
  95. data/lib/tasks/door_mat_tasks.rake +31 -0
  96. data/spec/controllers/door_mat/activities_controller_spec.rb +70 -0
  97. data/spec/controllers/door_mat/forgot_passwords_controller_spec.rb +57 -0
  98. data/spec/controllers/door_mat/manage_email_spec.rb +181 -0
  99. data/spec/controllers/door_mat/password_less_session_controller_spec.rb +344 -0
  100. data/spec/controllers/door_mat/sign_in_controller_spec.rb +211 -0
  101. data/spec/controllers/door_mat/sign_up_controller_spec.rb +90 -0
  102. data/spec/factories/door_mat_access_tokens.rb +6 -0
  103. data/spec/factories/door_mat_activitiess.rb +6 -0
  104. data/spec/factories/door_mat_actors.rb +23 -0
  105. data/spec/factories/door_mat_emails.rb +14 -0
  106. data/spec/factories/door_mat_memberships.rb +6 -0
  107. data/spec/factories/door_mat_sessions.rb +24 -0
  108. data/spec/features/password_less_session_spec.rb +165 -0
  109. data/spec/features/remember_me_spec.rb +672 -0
  110. data/spec/features/session_spec.rb +336 -0
  111. data/spec/lib/attr_store_spec.rb +237 -0
  112. data/spec/lib/crypto_spec.rb +130 -0
  113. data/spec/lib/process_spec.rb +159 -0
  114. data/spec/models/door_mat/access_token_spec.rb +134 -0
  115. data/spec/models/door_mat/activity_spec.rb +38 -0
  116. data/spec/models/door_mat/actor_spec.rb +56 -0
  117. data/spec/models/door_mat/email_spec.rb +25 -0
  118. data/spec/models/door_mat/session_spec.rb +69 -0
  119. data/spec/spec_helper.rb +223 -0
  120. data/spec/support/timecop/timecop_helper.rb +52 -0
  121. data/spec/test_app/README.rdoc +28 -0
  122. data/spec/test_app/Rakefile +6 -0
  123. data/spec/test_app/app/assets/javascripts/application.js +13 -0
  124. data/spec/test_app/app/assets/stylesheets/application.css +15 -0
  125. data/spec/test_app/app/controllers/account_controller.rb +28 -0
  126. data/spec/test_app/app/controllers/application_controller.rb +10 -0
  127. data/spec/test_app/app/controllers/password_less_sample_controller.rb +56 -0
  128. data/spec/test_app/app/controllers/static_controller.rb +7 -0
  129. data/spec/test_app/app/helpers/account_helper.rb +2 -0
  130. data/spec/test_app/app/helpers/application_helper.rb +2 -0
  131. data/spec/test_app/app/models/game.rb +62 -0
  132. data/spec/test_app/app/models/shared_data.rb +4 -0
  133. data/spec/test_app/app/models/shared_key.rb +8 -0
  134. data/spec/test_app/app/models/user_detail.rb +7 -0
  135. data/spec/test_app/app/views/account/show.html.erb +133 -0
  136. data/spec/test_app/app/views/door_mat/static/sign_out_success.html.erb +7 -0
  137. data/spec/test_app/app/views/layouts/application.html.erb +20 -0
  138. data/spec/test_app/app/views/password_less_sample/draw_results.html.erb +6 -0
  139. data/spec/test_app/app/views/password_less_sample/final_result.html.erb +7 -0
  140. data/spec/test_app/app/views/password_less_sample/play_game.html.erb +5 -0
  141. data/spec/test_app/app/views/password_less_sample/show_loosing_door.html.erb +10 -0
  142. data/spec/test_app/app/views/static/index.html.erb +12 -0
  143. data/spec/test_app/app/views/static/only_confirmed_email_allowed.html.erb +10 -0
  144. data/spec/test_app/app/views/static/page_that_require_password_reconfirmation.html.erb +16 -0
  145. data/spec/test_app/app/views/static/session_protected_page.html.erb +32 -0
  146. data/spec/test_app/bin/bundle +3 -0
  147. data/spec/test_app/bin/rails +4 -0
  148. data/spec/test_app/bin/rake +4 -0
  149. data/spec/test_app/config.ru +4 -0
  150. data/spec/test_app/config/application.rb +29 -0
  151. data/spec/test_app/config/boot.rb +5 -0
  152. data/spec/test_app/config/database.yml +25 -0
  153. data/spec/test_app/config/environment.rb +19 -0
  154. data/spec/test_app/config/environments/development.rb +50 -0
  155. data/spec/test_app/config/environments/production.rb +83 -0
  156. data/spec/test_app/config/environments/test.rb +48 -0
  157. data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
  158. data/spec/test_app/config/initializers/cookies_serializer.rb +3 -0
  159. data/spec/test_app/config/initializers/door_mat.rb +72 -0
  160. data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
  161. data/spec/test_app/config/initializers/inflections.rb +16 -0
  162. data/spec/test_app/config/initializers/mime_types.rb +4 -0
  163. data/spec/test_app/config/initializers/session_store.rb +3 -0
  164. data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
  165. data/spec/test_app/config/locales/en.yml +23 -0
  166. data/spec/test_app/config/routes.rb +42 -0
  167. data/spec/test_app/config/secrets.yml +31 -0
  168. data/spec/test_app/db/migrate/20140717182813_create_user_details.rb +10 -0
  169. data/spec/test_app/db/migrate/20140908225256_create_shared_data.rb +10 -0
  170. data/spec/test_app/db/migrate/20140908225604_create_shared_keys.rb +11 -0
  171. data/spec/test_app/db/migrate/20141121190714_create_games.rb +10 -0
  172. data/spec/test_app/public/404.html +67 -0
  173. data/spec/test_app/public/422.html +67 -0
  174. data/spec/test_app/public/500.html +66 -0
  175. data/spec/test_app/public/favicon.ico +0 -0
  176. metadata +552 -0
@@ -0,0 +1,61 @@
1
+ module DoorMat
2
+ class ManageEmailController < DoorMat::ApplicationController
3
+ before_action :require_password_reconfirm
4
+ before_action :require_confirmed_email
5
+ before_action :update_session_last_activity_time
6
+
7
+ def new
8
+ @email = DoorMat::Email.new
9
+ end
10
+
11
+ def create
12
+ @email = DoorMat::Email.for(manage_email_params[:address])
13
+
14
+ if DoorMat::Process::ManageEmail.add(@email, DoorMat::Session.current_session.actor, self)
15
+ flash[:notice] = I18n.t('door_mat.manage_email.email_added')
16
+
17
+ redirect_to config_url_redirect(:add_email_success_url)
18
+ else
19
+ render :new
20
+ end
21
+ end
22
+
23
+ def destroy
24
+ encoded_address = params[:email]
25
+ email = DoorMat::Session.current_session.actor.email_from_urlsafe_encoded(encoded_address)
26
+
27
+ if email.blank?
28
+ flash[:alert] = I18n.t('door_mat.manage_email.could_not_delete')
29
+ elsif 1 == DoorMat::Session.current_session.actor.emails.count
30
+ flash[:alert] = I18n.t('door_mat.manage_email.could_not_delete_only_email')
31
+ elsif email == DoorMat::Session.current_session.email
32
+ flash[:alert] = I18n.t('door_mat.manage_email.could_not_delete_current_email')
33
+ elsif email.primary?
34
+ flash[:alert] = I18n.t('door_mat.manage_email.can_not_delete_primary')
35
+ else
36
+ email.destroy!
37
+ flash[:notice] = I18n.t('door_mat.manage_email.email_deleted')
38
+ end
39
+
40
+ redirect_to config_url_redirect(:destroy_email_redirect_url)
41
+ end
42
+
43
+ def set_primary_email
44
+ encoded_address = params[:email]
45
+ if DoorMat::Process::ManageEmail.set_primary(encoded_address, DoorMat::Session.current_session.actor)
46
+ flash[:notice] = I18n.t('door_mat.manage_email.primary_email_updated')
47
+ else
48
+ flash[:alert] = I18n.t('door_mat.manage_email.could_not_update_primary_email')
49
+ end
50
+
51
+ redirect_to config_url_redirect(:set_primary_email_redirect_url)
52
+ end
53
+
54
+ private
55
+
56
+ def manage_email_params
57
+ params.require(:email).permit(:address)
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,121 @@
1
+ module DoorMat
2
+ class PasswordLessSessionController < DoorMat::ApplicationController
3
+ skip_before_action :require_valid_session, :only => [:new, :create, :access_token, :access_token_post]
4
+
5
+ def new
6
+ @access_token = DoorMat::AccessToken.new_with_token_for(params[:token_for], request)
7
+ end
8
+
9
+ def create
10
+ DoorMat::AccessToken.destroy_if_linked_to(cookies)
11
+
12
+ @access_token = DoorMat::AccessToken.create_from_params(params[:token_for],
13
+ access_token_params[:identifier],
14
+ access_token_params[:confirm_identifier],
15
+ access_token_params[:name],
16
+ access_token_params[:is_public],
17
+ access_token_params[:remember_me],
18
+ request)
19
+ if @access_token.errors.size > 0
20
+ render :new
21
+ else
22
+ @access_token.save!
23
+ deliver_token(@access_token)
24
+ redirect_to door_mat.access_token_token_for_token_url(@access_token.token_for)
25
+ end
26
+ end
27
+
28
+ def access_token
29
+ token_for = params[:token_for]
30
+ token = params[:token]
31
+
32
+ if token.blank?
33
+ @access_token = DoorMat::AccessToken.new_with_token_for(params[:token_for], request)
34
+ render :access_token
35
+ else
36
+ process_request(token_for, token)
37
+ end
38
+ end
39
+
40
+ def access_token_post
41
+ token_for = access_token_params[:token_for]
42
+ token = access_token_params[:identifier]
43
+ process_request(token_for, token)
44
+ end
45
+
46
+ private
47
+
48
+ def access_token_params
49
+ params.require(:access_token).permit(:identifier, :confirm_identifier, :token_for, :name, :is_public, :remember_me)
50
+ end
51
+
52
+ def process_request(token_for, token)
53
+ if process_token_request(token_for, token)
54
+ redirect_to session.delete(:redirect_to) || @access_token.default_success_url.inject(self) { |lhs, rhs| lhs.send(rhs) }
55
+ else
56
+ render_failed_token_request(token)
57
+ end
58
+
59
+ end
60
+
61
+ def render_failed_token_request(token)
62
+ if DoorMat::Regex.session_guid.match(token).blank?
63
+ flash.now[:alert] = "The format of your access token is invalid. Please verify there are no missing or extra characters."
64
+ else
65
+ flash.now[:alert] = "Something looks wrong with your access token. Please request a new one."
66
+ end
67
+ render :access_token
68
+ end
69
+
70
+ def process_token_request(token_for, token, klass = DoorMat::AccessToken)
71
+ klass.destroy_if_linked_to(cookies)
72
+ @access_token = nil
73
+
74
+ token_for_symbol = token_for.to_s.strip.to_sym
75
+ return false unless klass.token_for_is_valid(token_for_symbol)
76
+
77
+ @access_token = klass.validate_token(token, cookies, request)
78
+ unless @access_token.blank?
79
+ # This is a request so the token must have a status of :single_use or :multiple_use
80
+ if (@access_token.single_use? || @access_token.multiple_use?)
81
+
82
+ # mark single use tickets as used so they can't be reused
83
+ if @access_token.single_use?
84
+ @access_token.used!
85
+ end
86
+
87
+ validate = @access_token.session_parameters[:validate]
88
+ is_valid = true
89
+ is_valid = validate.call(@access_token.identifier) if validate
90
+
91
+ if is_valid
92
+ @access_token.set_up(cookies)
93
+ return true
94
+ else
95
+ DoorMat.configuration.logger.warn "WARN: #{request.remote_ip} Identifier #{@access_token.identifier} did not satisfy validation"
96
+ @access_token.destroy!
97
+ end
98
+
99
+ end
100
+ end
101
+
102
+ @access_token = klass.new_with_token_for(token_for_symbol, request)
103
+ return false
104
+ end
105
+
106
+ def deliver_token(access_token)
107
+ parameters = {
108
+ url_full: access_token_token_for_token_url(token_for: access_token.token_for, token: access_token.token, protocol: DoorMat::UrlProtocol.url_protocol),
109
+ url_short: access_token_token_for_token_url(token_for: access_token.token_for, protocol: DoorMat::UrlProtocol.url_protocol),
110
+ token: access_token.token,
111
+ address: access_token.identifier,
112
+ subject: "Your access token"
113
+ }
114
+ DoorMat::PasswordLessSessionMailer.send_token(parameters).deliver_now
115
+ rescue Exception => e
116
+ DoorMat.configuration.logger.error "ERROR: Failed to deliver access token to #{parameters[:address]} w #{parameters[:token]} - #{e}"
117
+ raise e
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,27 @@
1
+ module DoorMat
2
+ class ReconfirmPasswordController < DoorMat::ApplicationController
3
+ def new
4
+ @current_session_email = nil
5
+
6
+ # Following a
7
+ # require_password_reconfirm, provide the email associated with
8
+ # the current session.
9
+ # This convenience could be considered a leak of information
10
+ # so it is disabled by default.
11
+ if DoorMat.configuration.leak_email_address_at_reconfirm
12
+ @current_session_email = DoorMat::Session.current_session.email.address
13
+ end
14
+ end
15
+
16
+ def create
17
+ password = params[:password]
18
+ if DoorMat::Session.current_session.reconfirm_password(password)
19
+ destination_of_redirect = session.delete(:redirect_to) || config_url_redirect(:sign_in_success_url)
20
+ redirect_to destination_of_redirect
21
+ else
22
+ render :new
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ module DoorMat
2
+ class SessionsController < DoorMat::ApplicationController
3
+ before_action :require_confirmed_email
4
+
5
+ # This is to let the user terminate an existing session from a different browser or device
6
+ # see sign_in#destroy for the termination of the current active session in use
7
+ def terminate
8
+ session_guid = params[:guid]
9
+ Session.current_session.actor.sessions.where(hashed_token: session_guid).each do |session|
10
+ session.destroy
11
+ end
12
+
13
+ redirect_to :back
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,60 @@
1
+ module DoorMat
2
+ class SignInController < DoorMat::ApplicationController
3
+ skip_before_action :require_valid_session, :only => [:new, :create, :destroy]
4
+
5
+ def new
6
+ @sign_in = DoorMat::SignIn.new
7
+ end
8
+
9
+ def create
10
+ before_sign_in
11
+ @sign_in = DoorMat::SignIn.new(sign_in_params)
12
+
13
+ if @sign_in.valid? && DoorMat::Process::ActorSignIn.with(@sign_in.email, @sign_in.password, @sign_in.is_public?, @sign_in.remember_me?, request, cookies)
14
+ destination_of_redirect = session.delete(:redirect_to) || config_url_redirect(:sign_in_success_url)
15
+ reset_session
16
+
17
+ redirect_to destination_of_redirect
18
+ after_sign_in
19
+ else
20
+ @sign_in.add_generic_error_msg
21
+ render :new
22
+ after_failed_sign_in
23
+ end
24
+ end
25
+
26
+ def destroy
27
+ before_sign_out
28
+ DoorMat::Session.clear_current_session
29
+ DoorMat::Session.destroy_if_linked_to(cookies)
30
+
31
+ reset_session
32
+ redirect_to config_url_redirect(:sign_out_success_url)
33
+ after_sign_out
34
+
35
+ end
36
+
37
+ private
38
+
39
+ def sign_in_params
40
+ params.require(:sign_in).permit(:email, :password, :is_public, :remember_me)
41
+ end
42
+
43
+ def before_sign_in
44
+ DoorMat.configuration.event_hook_before_sign_in.each {|prc| prc.call}
45
+ end
46
+ def after_sign_in
47
+ DoorMat.configuration.event_hook_after_sign_in.each {|prc| prc.call}
48
+ end
49
+ def after_failed_sign_in
50
+ DoorMat.configuration.event_hook_after_failed_sign_in.each {|prc| prc.call}
51
+ end
52
+ def before_sign_out
53
+ DoorMat.configuration.event_hook_before_sign_out.each {|prc| prc.call}
54
+ end
55
+ def after_sign_out
56
+ DoorMat.configuration.event_hook_after_sign_out.each {|prc| prc.call}
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,59 @@
1
+ module DoorMat
2
+ class SignUpController < DoorMat::ApplicationController
3
+ skip_before_action :require_valid_session, :only => [:new, :create]
4
+
5
+ def new
6
+ @sign_up = DoorMat::SignUp.new
7
+ end
8
+
9
+ def create
10
+ before_sign_up
11
+ @sign_up = DoorMat::SignUp.new(sign_up_params)
12
+ sign_up_failed = true
13
+
14
+ if DoorMat.configuration.allow_sign_up && @sign_up.valid?
15
+
16
+ if DoorMat.configuration.allow_sign_in_from_sign_up_form && DoorMat::Process::ActorSignIn.with(@sign_up.email, @sign_up.password, true, false, request, cookies)
17
+ destination_of_redirect = session.delete(:redirect_to) || config_url_redirect(:sign_in_success_url)
18
+ reset_session
19
+
20
+ redirect_to destination_of_redirect
21
+ after_sign_in
22
+ sign_up_failed = false
23
+ elsif DoorMat::Process::ActorSignUp.with(@sign_up.email, @sign_up.password, request, cookies, self)
24
+ reset_session
25
+ redirect_to config_url_redirect(:sign_up_success_url)
26
+ after_sign_up
27
+ sign_up_failed = false
28
+ end
29
+
30
+ end
31
+
32
+ if sign_up_failed
33
+ @sign_up.add_generic_error_msg
34
+ render :new
35
+ after_failed_sign_up
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def sign_up_params
42
+ params.require(:sign_up).permit(:email, :password, :password_confirmation)
43
+ end
44
+
45
+ def after_sign_in
46
+ DoorMat.configuration.event_hook_after_sign_in.each {|prc| prc.call}
47
+ end
48
+ def before_sign_up
49
+ DoorMat.configuration.event_hook_before_sign_up.each {|prc| prc.call}
50
+ end
51
+ def after_sign_up
52
+ DoorMat.configuration.event_hook_after_sign_up.each {|prc| prc.call}
53
+ end
54
+ def after_failed_sign_up
55
+ DoorMat.configuration.event_hook_after_failed_sign_up.each {|prc| prc.call}
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,5 @@
1
+ module DoorMat
2
+ class StaticController < DoorMat::ApplicationController
3
+ skip_before_action :require_valid_session, :only => [:sign_out_success, :forgot_password_verification_mail_sent]
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ module DoorMat
2
+ class ActivityMailer < ActionMailer::Base
3
+ default from: DoorMat.configuration.mailer_from_address
4
+
5
+ def confirm_email(parameters)
6
+ @parameters = parameters
7
+
8
+ mail to: @parameters[:address], subject: (@parameters[:subject] || I18n.t("door_mat.activity_mailer.confirm_email.subject"))
9
+ end
10
+
11
+ def reset_password(parameters)
12
+ @parameters = parameters
13
+
14
+ mail to: @parameters[:address], subject: (@parameters[:subject] || I18n.t("door_mat.activity_mailer.reset_password.subject"))
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ module DoorMat
2
+ class PasswordLessSessionMailer < ActionMailer::Base
3
+ default from: DoorMat.configuration.mailer_from_address
4
+
5
+ def send_token(parameters)
6
+ @parameters = parameters
7
+
8
+ mail to: @parameters[:address], subject: (@parameters[:subject] || I18n.t("door_mat.password_less_session_mailer.send_token.subject"))
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,315 @@
1
+ module DoorMat
2
+ class AccessToken < ActiveRecord::Base
3
+
4
+ include DoorMat::AttrSymmetricStore
5
+
6
+ belongs_to :actor, class_name: 'DoorMat::Actor'
7
+
8
+ attr_symmetric_store :name, :identifier, :data
9
+
10
+ enum token_for: (k = DoorMat.configuration.password_less_sessions.keys; k.delete(:password_less_defaults); k)
11
+ enum status: [:single_use, :multiple_use, :used]
12
+ enum rating: [:public_computer, :private_computer, :remember_me]
13
+
14
+ attr_accessor :token, :is_public, :remember_me
15
+
16
+ after_initialize :init
17
+
18
+ def init
19
+ self.is_public = true if self.is_public.nil?
20
+ self.remember_me = false if self.remember_me.nil?
21
+ end
22
+
23
+ validate :initialization_performed?
24
+
25
+ def initialization_performed?
26
+ if self.actor.blank? || self.hashed_token.blank?
27
+ errors.add(:base, "Access token invalid")
28
+ end
29
+ end
30
+
31
+ def self.current_access_token
32
+ RequestStore.store[:current_access_token] ||= self.new
33
+ end
34
+
35
+ def self.clear_current_access_token
36
+ access_token = current_access_token
37
+ RequestStore.store[:current_access_token] = nil
38
+
39
+ access_token.destroy if access_token.persisted?
40
+ nil
41
+ end
42
+
43
+ def self.token_for_is_valid(token_for_symbol)
44
+ DoorMat.configuration.password_less_sessions.has_key? token_for_symbol
45
+ end
46
+
47
+ def self.new_with_token_for(token_for, request)
48
+ access_token = self.new
49
+ token_for_symbol = token_for.to_s.strip.to_sym
50
+ if token_for_is_valid(token_for_symbol)
51
+ access_token.token_for = self.token_fors[token_for_symbol]
52
+ else
53
+ DoorMat.configuration.logger.warn "WARN: #{request.remote_ip} Attempted to use inexistent token_for #{token_for}"
54
+ access_token.errors[:base] << I18n.t("door_mat.password_less_session.create_failed")
55
+ end
56
+ access_token
57
+ end
58
+
59
+ def self.create_from_params(token_for, identifier, confirm_identifier, name, is_public, remember_me, request)
60
+ clear_current_access_token
61
+ is_public = '1' == is_public.to_s
62
+ remember_me = '1' == remember_me.to_s
63
+
64
+ access_token = new_with_token_for(token_for, request)
65
+ return access_token unless access_token.errors.blank?
66
+
67
+ access_token.identifier = identifier
68
+ access_token.name = name || 'access token'
69
+
70
+ if access_token.identifier.blank?
71
+ access_token.errors[:identifier] << I18n.t("door_mat.password_less_session.blank_identifier")
72
+ return access_token
73
+ end
74
+
75
+ if access_token.session_parameters[:challenge].include? :email
76
+ if DoorMat::Regex.simple_email.match(access_token.identifier).blank?
77
+ access_token.errors[:identifier] << I18n.t("door_mat.password_less_session.expect_email_identifier")
78
+ return access_token
79
+ end
80
+ end
81
+
82
+ unless identifier == confirm_identifier
83
+ access_token.errors[:identifier] << I18n.t("door_mat.password_less_session.identifier_error")
84
+ return access_token
85
+ end
86
+
87
+ unless [:single_use, :multiple_use].include? access_token.session_parameters[:status]
88
+ DoorMat.configuration.logger.error "ERROR: #{request.remote_ip} Status must be either :single_use or :multiple_use check your configuration - found #{access_token.session_parameters[:status]}"
89
+ access_token.errors[:base] << I18n.t("door_mat.password_less_session.create_failed")
90
+ return access_token
91
+ end
92
+ access_token.status = access_token.session_parameters[:status]
93
+
94
+ unless access_token.load_sub_session
95
+ access_token.errors[:base] << I18n.t("door_mat.password_less_session.actor_missing")
96
+ return access_token
97
+ end
98
+
99
+ access_token.generate_new_token
100
+
101
+ if is_public
102
+ access_token.public_computer!
103
+ else
104
+ access_token.private_computer!
105
+ end
106
+
107
+ # User asked to be remembered
108
+ if DoorMat.configuration.allow_remember_me_feature && remember_me
109
+ access_token.remember_me! unless (
110
+ DoorMat.configuration.remember_me_require_private_computer_confirmation &&
111
+ access_token.public_computer?
112
+ )
113
+ end
114
+
115
+ access_token
116
+ end
117
+
118
+ def form_submit_path(controller)
119
+ session_parameters.fetch(:form_submit_path).inject(controller) { |lhs, rhs| lhs.send(rhs) }
120
+ end
121
+
122
+ def generate_new_token
123
+ @token = SecureRandom.uuid
124
+ self.hashed_token = DoorMat::Crypto::FastHash.sha256(@token)
125
+ end
126
+
127
+ def session_parameters
128
+ @session_params ||= DoorMat.configuration.password_less_sessions[self.token_for.to_sym]
129
+ end
130
+
131
+ def default_parameters
132
+ @default_parameters ||= DoorMat.configuration.password_less_sessions[:password_less_defaults]
133
+ end
134
+
135
+ def default_failure_url
136
+ session_parameters.fetch(:default_failure_url, generic_redirect_url)
137
+ end
138
+
139
+ def default_success_url
140
+ session_parameters.fetch(:default_success_url, generic_redirect_url)
141
+ end
142
+
143
+ def generic_redirect_url
144
+ default_parameters.fetch(:generic_redirect_url, [:main_app, :root_url])
145
+ end
146
+
147
+ def load_actor_for_session
148
+ self.actor ||= DoorMat::Actor.authenticate_with(self.session_parameters[:actor][:email], self.session_parameters[:actor][:password])
149
+ if self.actor.nil?
150
+ DoorMat.configuration.logger.error "ERROR: Could not authenticate actor #{self.session_parameters[:actor][:email]} is it in your database?"
151
+ return false
152
+ end
153
+ true
154
+ end
155
+
156
+ def load_sub_session
157
+ return false unless load_actor_for_session
158
+
159
+ unless DoorMat::Session.current_session.session_for_actor_loaded? self.actor
160
+ sub_session = DoorMat::Session.new_sub_session_for_actor(self.actor, self.session_parameters[:actor][:password])
161
+ DoorMat::Session.current_session.append_sub_session(sub_session)
162
+ end
163
+ true
164
+ end
165
+
166
+ def self.swap_token!(cookies, valid_current_session_tokens, new_session_token, force_new_token_generation = false)
167
+ access_token = current_access_token
168
+
169
+ # Our current access token is in order
170
+ return unless access_token.valid?
171
+
172
+ valid_transitions = access_token.session_parameters.fetch(:transitions, [])
173
+ # The current access token is for one of the valid_current_session_tokens
174
+ return unless Array(valid_current_session_tokens).include? access_token.token_for.to_sym
175
+ # The transition is valid
176
+ return unless valid_transitions.include? new_session_token
177
+
178
+ blank_new_session_access_token = self.new
179
+ blank_new_session_access_token.token_for = self.token_fors[new_session_token]
180
+ return unless blank_new_session_access_token.load_actor_for_session
181
+ issue_new_token = force_new_token_generation || access_token.multiple_use? || (access_token.actor_id != blank_new_session_access_token.actor_id)
182
+
183
+ if issue_new_token
184
+ RequestStore.store[:current_access_token] = blank_new_session_access_token
185
+
186
+ if access_token.used?
187
+ access_token.destroy!
188
+ end
189
+
190
+ blank_new_session_access_token.renew_token(cookies)
191
+ blank_new_session_access_token.name = access_token.name
192
+ blank_new_session_access_token.identifier = access_token.identifier
193
+ blank_new_session_access_token.data = access_token.data
194
+ blank_new_session_access_token.reference_id = access_token.reference_id
195
+ blank_new_session_access_token.used!
196
+ else
197
+ # For a single user ticket, just update the token_for value
198
+ if access_token.used?
199
+ access_token.token_for = self.token_fors[new_session_token]
200
+ access_token.save!
201
+ end
202
+ end
203
+ end
204
+
205
+ def self.is_cookie_present?(cookies)
206
+ !cookies.encrypted[:token].blank?
207
+ end
208
+
209
+ def self.load_token(token, request, verbose=true)
210
+ token = token.to_s.strip
211
+ if DoorMat::Regex.session_guid.match(token).blank?
212
+ DoorMat.configuration.logger.warn "WARN: #{request.remote_ip} Attempted to use token with invalid format #{token}" if verbose
213
+ return nil
214
+ end
215
+
216
+ access_token = self.find_by_hashed_token DoorMat::Crypto::FastHash.sha256(token)
217
+ if access_token.blank?
218
+ DoorMat.configuration.logger.warn "WARN: #{request.remote_ip} Attempted to use inexistent token #{token}" if verbose
219
+ return nil
220
+ end
221
+
222
+ return nil unless access_token.load_sub_session
223
+
224
+ # Reload the token using find now that the sub_session is loaded
225
+ # to allow the encrypted field to be decrypted
226
+ # the request hits the cache so there is no additional round trip to the DB
227
+ access_token = self.find(access_token.id)
228
+ access_token.token = token
229
+ access_token
230
+ end
231
+
232
+ def renew_token(cookies)
233
+ generate_new_token
234
+
235
+ set_up(cookies)
236
+ end
237
+
238
+ def self.validate_token(token, cookies, request)
239
+ access_token = load_token(token, request)
240
+
241
+ access_token = case
242
+ when access_token.blank?
243
+ nil
244
+ # For an unused token, check if the link has expired against access_token.session_parameters[:expiration_delay].ago
245
+ when access_token.single_use?, access_token.multiple_use?
246
+ if access_token.created_at < access_token.session_parameters[:expiration_delay].ago
247
+ DoorMat.configuration.logger.info "INFO: #{request.remote_ip} Attempted to use expired token #{token}"
248
+ access_token.destroy!
249
+ nil
250
+ else
251
+ access_token
252
+ end
253
+ # otherwise, for an ongoing session, use public / private / remember_me expiration delay
254
+ when access_token.public_computer?
255
+ if access_token.updated_at < DoorMat.configuration.public_computer_access_session_timeout.minutes.ago
256
+ access_token.destroy!
257
+ nil
258
+ else
259
+ access_token
260
+ end
261
+ when access_token.private_computer?
262
+ if access_token.updated_at < DoorMat.configuration.private_computer_access_session_timeout.minutes.ago
263
+ access_token.destroy!
264
+ nil
265
+ else
266
+ access_token
267
+ end
268
+ when access_token.remember_me?
269
+ if access_token.created_at < DoorMat.configuration.remember_me_max_day_count.days.ago
270
+ access_token.destroy!
271
+ nil
272
+ else
273
+ if access_token.updated_at < DoorMat.configuration.private_computer_access_session_timeout.minutes.ago
274
+ access_token.renew_token(cookies)
275
+ access_token.save
276
+ end
277
+ access_token
278
+ end
279
+ end
280
+
281
+ access_token
282
+ end
283
+
284
+ def self.validate_from_cookie(cookies, request)
285
+ token = cookies.encrypted[:token]
286
+ RequestStore.store[:current_access_token] = validate_token(token, cookies, request)
287
+ clean_up(cookies) if RequestStore.store[:current_access_token].nil?
288
+ end
289
+
290
+ def self.destroy_if_linked_to(cookies)
291
+ token = cookies.encrypted[:token] || ''
292
+ clean_up(cookies)
293
+
294
+ access_token = load_token(token, nil, false)
295
+ return false if access_token.blank?
296
+
297
+ access_token.destroy! unless access_token.multiple_use?
298
+ true
299
+ end
300
+
301
+ def set_up(cookies)
302
+ options = {
303
+ secure: DoorMat.configuration.transmit_cookies_only_over_https,
304
+ httponly: true
305
+ }
306
+ options.merge!({ expires: DoorMat.configuration.remember_me_max_day_count.days.since(self.created_at) }) unless self.public_computer?
307
+ cookies.encrypted[:token] = options.merge({value: self.token})
308
+ end
309
+
310
+ def self.clean_up(cookies)
311
+ cookies.delete(:token)
312
+ end
313
+
314
+ end
315
+ end