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.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +88 -0
- data/Rakefile +32 -0
- data/app/assets/javascripts/door_mat/application.js +13 -0
- data/app/assets/stylesheets/door_mat/application.css +15 -0
- data/app/assets/stylesheets/scaffold.css +56 -0
- data/app/controllers/door_mat/activities_controller.rb +106 -0
- data/app/controllers/door_mat/application_controller.rb +14 -0
- data/app/controllers/door_mat/change_password_controller.rb +32 -0
- data/app/controllers/door_mat/forgot_passwords_controller.rb +57 -0
- data/app/controllers/door_mat/manage_email_controller.rb +61 -0
- data/app/controllers/door_mat/password_less_session_controller.rb +121 -0
- data/app/controllers/door_mat/reconfirm_password_controller.rb +27 -0
- data/app/controllers/door_mat/sessions_controller.rb +17 -0
- data/app/controllers/door_mat/sign_in_controller.rb +60 -0
- data/app/controllers/door_mat/sign_up_controller.rb +59 -0
- data/app/controllers/door_mat/static_controller.rb +5 -0
- data/app/mailers/door_mat/activity_mailer.rb +18 -0
- data/app/mailers/door_mat/password_less_session_mailer.rb +12 -0
- data/app/models/door_mat/access_token.rb +315 -0
- data/app/models/door_mat/activity.rb +14 -0
- data/app/models/door_mat/activity_confirm_email.rb +45 -0
- data/app/models/door_mat/activity_download_recovery_key.rb +30 -0
- data/app/models/door_mat/activity_reset_password.rb +47 -0
- data/app/models/door_mat/actor.rb +149 -0
- data/app/models/door_mat/change_password.rb +12 -0
- data/app/models/door_mat/email.rb +58 -0
- data/app/models/door_mat/forgot_password.rb +12 -0
- data/app/models/door_mat/membership.rb +42 -0
- data/app/models/door_mat/session.rb +315 -0
- data/app/models/door_mat/sign_in.rb +31 -0
- data/app/models/door_mat/sign_up.rb +17 -0
- data/app/views/door_mat/activity_mailer/confirm_email.html.erb +11 -0
- data/app/views/door_mat/activity_mailer/confirm_email.text.erb +7 -0
- data/app/views/door_mat/activity_mailer/reset_password.html.erb +11 -0
- data/app/views/door_mat/activity_mailer/reset_password.text.erb +7 -0
- data/app/views/door_mat/change_password/new.html.erb +22 -0
- data/app/views/door_mat/forgot_passwords/choose_new_password.html.erb +34 -0
- data/app/views/door_mat/forgot_passwords/new.html.erb +14 -0
- data/app/views/door_mat/helpers/_errors_if_any.html.erb +10 -0
- data/app/views/door_mat/manage_email/new.html.erb +14 -0
- data/app/views/door_mat/password_less_session/access_token.html.erb +16 -0
- data/app/views/door_mat/password_less_session/new.html.erb +34 -0
- data/app/views/door_mat/password_less_session_mailer/send_token.html.erb +11 -0
- data/app/views/door_mat/password_less_session_mailer/send_token.text.erb +7 -0
- data/app/views/door_mat/reconfirm_password/new.html.erb +12 -0
- data/app/views/door_mat/sign_in/new.html.erb +30 -0
- data/app/views/door_mat/sign_up/new.html.erb +24 -0
- data/app/views/door_mat/static/add_email_success.html.erb +5 -0
- data/app/views/door_mat/static/change_password_success.html.erb +2 -0
- data/app/views/door_mat/static/confirm_email_success.html.erb +2 -0
- data/app/views/door_mat/static/email_confirmation_required.html.erb +17 -0
- data/app/views/door_mat/static/forgot_password_verification_mail_sent.html.erb +2 -0
- data/app/views/door_mat/static/reconfirm_password_success.html.erb +4 -0
- data/app/views/door_mat/static/sign_in_success.html.erb +5 -0
- data/app/views/door_mat/static/sign_out_success.html.erb +5 -0
- data/app/views/door_mat/static/sign_up_success.html.erb +4 -0
- data/bin/rails +12 -0
- data/config/locales/en.yml +73 -0
- data/config/routes.rb +48 -0
- data/db/migrate/20140616234935_create_door_mat_actors.rb +23 -0
- data/db/migrate/20140617233357_create_door_mat_sessions.rb +17 -0
- data/db/migrate/20140630043202_create_door_mat_emails.rb +12 -0
- data/db/migrate/20140702045729_create_door_mat_activities.rb +14 -0
- data/db/migrate/20141115183045_create_door_mat_access_tokens.rb +17 -0
- data/db/migrate/20141121191824_create_door_mat_memberships.rb +14 -0
- data/db/migrate/20150910182126_rename_session_guid_column.rb +5 -0
- data/db/migrate/20150918210831_add_access_token_rating_column.rb +5 -0
- data/door_mat.gemspec +37 -0
- data/lib/door_mat.rb +20 -0
- data/lib/door_mat/attr_asymmetric_store.rb +82 -0
- data/lib/door_mat/attr_symmetric_store.rb +82 -0
- data/lib/door_mat/configuration.rb +193 -0
- data/lib/door_mat/controller.rb +117 -0
- data/lib/door_mat/crypto.rb +49 -0
- data/lib/door_mat/crypto/asymmetric_store.rb +77 -0
- data/lib/door_mat/crypto/fast_hash.rb +17 -0
- data/lib/door_mat/crypto/password_hash.rb +39 -0
- data/lib/door_mat/crypto/secure_compare.rb +23 -0
- data/lib/door_mat/crypto/symmetric_store.rb +68 -0
- data/lib/door_mat/engine.rb +23 -0
- data/lib/door_mat/process/actor_password_change.rb +65 -0
- data/lib/door_mat/process/actor_sign_in.rb +38 -0
- data/lib/door_mat/process/actor_sign_up.rb +39 -0
- data/lib/door_mat/process/create_new_anonymous_actor.rb +36 -0
- data/lib/door_mat/process/manage_email.rb +42 -0
- data/lib/door_mat/process/reset_password.rb +50 -0
- data/lib/door_mat/regex.rb +17 -0
- data/lib/door_mat/test_helper.rb +58 -0
- data/lib/door_mat/url_protocol.rb +9 -0
- data/lib/door_mat/version.rb +3 -0
- data/lib/tasks/door_mat_tasks.rake +31 -0
- data/spec/controllers/door_mat/activities_controller_spec.rb +70 -0
- data/spec/controllers/door_mat/forgot_passwords_controller_spec.rb +57 -0
- data/spec/controllers/door_mat/manage_email_spec.rb +181 -0
- data/spec/controllers/door_mat/password_less_session_controller_spec.rb +344 -0
- data/spec/controllers/door_mat/sign_in_controller_spec.rb +211 -0
- data/spec/controllers/door_mat/sign_up_controller_spec.rb +90 -0
- data/spec/factories/door_mat_access_tokens.rb +6 -0
- data/spec/factories/door_mat_activitiess.rb +6 -0
- data/spec/factories/door_mat_actors.rb +23 -0
- data/spec/factories/door_mat_emails.rb +14 -0
- data/spec/factories/door_mat_memberships.rb +6 -0
- data/spec/factories/door_mat_sessions.rb +24 -0
- data/spec/features/password_less_session_spec.rb +165 -0
- data/spec/features/remember_me_spec.rb +672 -0
- data/spec/features/session_spec.rb +336 -0
- data/spec/lib/attr_store_spec.rb +237 -0
- data/spec/lib/crypto_spec.rb +130 -0
- data/spec/lib/process_spec.rb +159 -0
- data/spec/models/door_mat/access_token_spec.rb +134 -0
- data/spec/models/door_mat/activity_spec.rb +38 -0
- data/spec/models/door_mat/actor_spec.rb +56 -0
- data/spec/models/door_mat/email_spec.rb +25 -0
- data/spec/models/door_mat/session_spec.rb +69 -0
- data/spec/spec_helper.rb +223 -0
- data/spec/support/timecop/timecop_helper.rb +52 -0
- data/spec/test_app/README.rdoc +28 -0
- data/spec/test_app/Rakefile +6 -0
- data/spec/test_app/app/assets/javascripts/application.js +13 -0
- data/spec/test_app/app/assets/stylesheets/application.css +15 -0
- data/spec/test_app/app/controllers/account_controller.rb +28 -0
- data/spec/test_app/app/controllers/application_controller.rb +10 -0
- data/spec/test_app/app/controllers/password_less_sample_controller.rb +56 -0
- data/spec/test_app/app/controllers/static_controller.rb +7 -0
- data/spec/test_app/app/helpers/account_helper.rb +2 -0
- data/spec/test_app/app/helpers/application_helper.rb +2 -0
- data/spec/test_app/app/models/game.rb +62 -0
- data/spec/test_app/app/models/shared_data.rb +4 -0
- data/spec/test_app/app/models/shared_key.rb +8 -0
- data/spec/test_app/app/models/user_detail.rb +7 -0
- data/spec/test_app/app/views/account/show.html.erb +133 -0
- data/spec/test_app/app/views/door_mat/static/sign_out_success.html.erb +7 -0
- data/spec/test_app/app/views/layouts/application.html.erb +20 -0
- data/spec/test_app/app/views/password_less_sample/draw_results.html.erb +6 -0
- data/spec/test_app/app/views/password_less_sample/final_result.html.erb +7 -0
- data/spec/test_app/app/views/password_less_sample/play_game.html.erb +5 -0
- data/spec/test_app/app/views/password_less_sample/show_loosing_door.html.erb +10 -0
- data/spec/test_app/app/views/static/index.html.erb +12 -0
- data/spec/test_app/app/views/static/only_confirmed_email_allowed.html.erb +10 -0
- data/spec/test_app/app/views/static/page_that_require_password_reconfirmation.html.erb +16 -0
- data/spec/test_app/app/views/static/session_protected_page.html.erb +32 -0
- data/spec/test_app/bin/bundle +3 -0
- data/spec/test_app/bin/rails +4 -0
- data/spec/test_app/bin/rake +4 -0
- data/spec/test_app/config.ru +4 -0
- data/spec/test_app/config/application.rb +29 -0
- data/spec/test_app/config/boot.rb +5 -0
- data/spec/test_app/config/database.yml +25 -0
- data/spec/test_app/config/environment.rb +19 -0
- data/spec/test_app/config/environments/development.rb +50 -0
- data/spec/test_app/config/environments/production.rb +83 -0
- data/spec/test_app/config/environments/test.rb +48 -0
- data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/test_app/config/initializers/cookies_serializer.rb +3 -0
- data/spec/test_app/config/initializers/door_mat.rb +72 -0
- data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/test_app/config/initializers/inflections.rb +16 -0
- data/spec/test_app/config/initializers/mime_types.rb +4 -0
- data/spec/test_app/config/initializers/session_store.rb +3 -0
- data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/test_app/config/locales/en.yml +23 -0
- data/spec/test_app/config/routes.rb +42 -0
- data/spec/test_app/config/secrets.yml +31 -0
- data/spec/test_app/db/migrate/20140717182813_create_user_details.rb +10 -0
- data/spec/test_app/db/migrate/20140908225256_create_shared_data.rb +10 -0
- data/spec/test_app/db/migrate/20140908225604_create_shared_keys.rb +11 -0
- data/spec/test_app/db/migrate/20141121190714_create_games.rb +10 -0
- data/spec/test_app/public/404.html +67 -0
- data/spec/test_app/public/422.html +67 -0
- data/spec/test_app/public/500.html +66 -0
- data/spec/test_app/public/favicon.ico +0 -0
- 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,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
|