door_mat 0.0.5

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 (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