door_mat 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|