booth 0.0.1
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/CHANGELOG.md +4 -0
- data/LICENSE.md +22 -0
- data/README.md +372 -0
- data/app/assets/config/booth_manifest.js +15 -0
- data/app/assets/images/booth/browsers/README.md +2 -0
- data/app/assets/images/booth/browsers/chrome.svg +1 -0
- data/app/assets/images/booth/browsers/edge.svg +1 -0
- data/app/assets/images/booth/browsers/firefox.svg +1 -0
- data/app/assets/images/booth/browsers/internet_explorer.svg +1 -0
- data/app/assets/images/booth/browsers/opera.svg +1 -0
- data/app/assets/images/booth/browsers/safari.svg +1 -0
- data/app/assets/images/booth/browsers/unknown.svg +1 -0
- data/app/assets/images/booth/platforms/README.md +2 -0
- data/app/assets/images/booth/platforms/android.svg +6 -0
- data/app/assets/images/booth/platforms/apple.svg +6 -0
- data/app/assets/images/booth/platforms/linux.svg +6 -0
- data/app/assets/images/booth/platforms/unknown.svg +1 -0
- data/app/assets/images/booth/platforms/windows.svg +6 -0
- data/app/assets/javascripts/booth/all.js +162 -0
- data/app/assets/javascripts/booth/all.js.map +1 -0
- data/app/assets/javascripts/booth/booth.ts +194 -0
- data/app/assets/javascripts/booth/webauthn-json.ts +99 -0
- data/config/locales/de.yml +84 -0
- data/config/locales/en.yml +79 -0
- data/lib/booth/adminland/credentials/create.rb +30 -0
- data/lib/booth/adminland/onboardings/create.rb +63 -0
- data/lib/booth/adminland/onboardings/destroy.rb +50 -0
- data/lib/booth/adminland/onboardings/find.rb +93 -0
- data/lib/booth/adminland/onboardings/index.rb +23 -0
- data/lib/booth/adminland/periodic_cleanup.rb +11 -0
- data/lib/booth/adminland/recoveries/consume.rb +70 -0
- data/lib/booth/adminland.rb +48 -0
- data/lib/booth/audits/register/added_otp.rb +22 -0
- data/lib/booth/audits/register/changed_otp.rb +22 -0
- data/lib/booth/audits/register/completed_onboarding.rb +22 -0
- data/lib/booth/audits/register/correct_otp.rb +42 -0
- data/lib/booth/audits/register/correct_password.rb +43 -0
- data/lib/booth/audits/register/logout.rb +22 -0
- data/lib/booth/audits/register/requested_password_reset.rb +22 -0
- data/lib/booth/audits/register/wrong_otp.rb +22 -0
- data/lib/booth/audits/register/wrong_password.rb +25 -0
- data/lib/booth/authenticators/confirm.rb +34 -0
- data/lib/booth/authenticators/credential_mode_after_confirmation.rb +25 -0
- data/lib/booth/authenticators/step.rb +19 -0
- data/lib/booth/concerns/action.rb +58 -0
- data/lib/booth/concerns/transition.rb +17 -0
- data/lib/booth/configuration.rb +116 -0
- data/lib/booth/configure.rb +37 -0
- data/lib/booth/contests/get.rb +36 -0
- data/lib/booth/contests/respond.rb +78 -0
- data/lib/booth/contests/set_for_login.rb +28 -0
- data/lib/booth/cooldowns/distance_of_time.rb +46 -0
- data/lib/booth/cooldowns/otp.rb +22 -0
- data/lib/booth/cooldowns/password.rb +44 -0
- data/lib/booth/cooldowns/password_reset.rb +24 -0
- data/lib/booth/cooldowns/strategies/exponential.rb +82 -0
- data/lib/booth/cooldowns/strategies/global.rb +62 -0
- data/lib/booth/cooldowns/strategies/result.rb +22 -0
- data/lib/booth/credentials/create.rb +28 -0
- data/lib/booth/credentials/create_with_onboarding.rb +26 -0
- data/lib/booth/credentials/find_by_username.rb +45 -0
- data/lib/booth/credentials/mode.rb +69 -0
- data/lib/booth/credentials/modes/otp_addable.rb +23 -0
- data/lib/booth/credentials/modes/otp_changeable.rb +23 -0
- data/lib/booth/credentials/modes/otp_manageable.rb +17 -0
- data/lib/booth/credentials/modes/otp_removable.rb +23 -0
- data/lib/booth/credentials/modes/password_addable.rb +29 -0
- data/lib/booth/credentials/modes/password_changeable.rb +31 -0
- data/lib/booth/credentials/modes/password_manageable.rb +17 -0
- data/lib/booth/credentials/modes/password_removable.rb +24 -0
- data/lib/booth/credentials/modes/password_removal_requires_user_verifiable_webauth.rb +16 -0
- data/lib/booth/credentials/modes/webauth_addable.rb +26 -0
- data/lib/booth/credentials/modes/webauth_manageable.rb +16 -0
- data/lib/booth/credentials/modes/webauth_removable.rb +25 -0
- data/lib/booth/credentials/otp_authentication.rb +59 -0
- data/lib/booth/credentials/password_authentication.rb +72 -0
- data/lib/booth/credentials/webauth_challenge.rb +28 -0
- data/lib/booth/engine.rb +25 -0
- data/lib/booth/errors.rb +86 -0
- data/lib/booth/geolocation.rb +20 -0
- data/lib/booth/hooks/after_fetch.rb +54 -0
- data/lib/booth/hooks/before_logout.rb +29 -0
- data/lib/booth/hooks/serialize_from_session.rb +24 -0
- data/lib/booth/hooks/serialize_into_session.rb +14 -0
- data/lib/booth/logger.rb +41 -0
- data/lib/booth/logging.rb +59 -0
- data/lib/booth/method_object.rb +73 -0
- data/lib/booth/mode.rb +22 -0
- data/lib/booth/models/application_record.rb +7 -0
- data/lib/booth/models/audit.rb +24 -0
- data/lib/booth/models/authenticator.rb +45 -0
- data/lib/booth/models/concerns/modeable.rb +50 -0
- data/lib/booth/models/concerns/otpable.rb +37 -0
- data/lib/booth/models/concerns/passwordable.rb +58 -0
- data/lib/booth/models/contest.rb +55 -0
- data/lib/booth/models/contests/scopes/recently_created.rb +23 -0
- data/lib/booth/models/contests/scopes/recently_responded.rb +32 -0
- data/lib/booth/models/credential.rb +61 -0
- data/lib/booth/models/onboarding.rb +61 -0
- data/lib/booth/models/password_reset.rb +41 -0
- data/lib/booth/models/recovery.rb +32 -0
- data/lib/booth/models/registration.rb +10 -0
- data/lib/booth/models/session.rb +47 -0
- data/lib/booth/models/user_agent.rb +50 -0
- data/lib/booth/modes/base.rb +25 -0
- data/lib/booth/modes/username_and_password.rb +7 -0
- data/lib/booth/modes/username_and_webauth.rb +7 -0
- data/lib/booth/modes/username_password_and_otp.rb +7 -0
- data/lib/booth/modes/username_password_and_webauth.rb +7 -0
- data/lib/booth/onboardings/find.rb +35 -0
- data/lib/booth/onboardings/propagate_to_credential.rb +63 -0
- data/lib/booth/onboardings/step.rb +68 -0
- data/lib/booth/password_resets/create.rb +57 -0
- data/lib/booth/password_resets/find.rb +36 -0
- data/lib/booth/password_resets/propagate_to_credential.rb +36 -0
- data/lib/booth/password_resets/step.rb +18 -0
- data/lib/booth/recoveries/create.rb +45 -0
- data/lib/booth/request.rb +106 -0
- data/lib/booth/requests/agent.rb +14 -0
- data/lib/booth/requests/authentication.rb +47 -0
- data/lib/booth/requests/ip.rb +28 -0
- data/lib/booth/requests/return_path.rb +34 -0
- data/lib/booth/requests/session.rb +106 -0
- data/lib/booth/requests/storage.rb +62 -0
- data/lib/booth/requests/storages/login.rb +108 -0
- data/lib/booth/requests/storages/otp.rb +54 -0
- data/lib/booth/requests/storages/password.rb +49 -0
- data/lib/booth/requests/storages/password_reset.rb +35 -0
- data/lib/booth/requests/storages/recovery.rb +35 -0
- data/lib/booth/requests/storages/registration.rb +27 -0
- data/lib/booth/requests/storages/webauth.rb +38 -0
- data/lib/booth/requests/sudo.rb +110 -0
- data/lib/booth/routes/userland.rb +80 -0
- data/lib/booth/sessions/create_and_login.rb +46 -0
- data/lib/booth/sessions/historical_locations.rb +18 -0
- data/lib/booth/sessions/index.rb +59 -0
- data/lib/booth/sessions/revoke.rb +51 -0
- data/lib/booth/sessions/revoke_all_others.rb +43 -0
- data/lib/booth/sessions/to_passport.rb +51 -0
- data/lib/booth/syntaxes/contest_code.rb +58 -0
- data/lib/booth/syntaxes/email.rb +97 -0
- data/lib/booth/syntaxes/ip.rb +37 -0
- data/lib/booth/syntaxes/otp.rb +57 -0
- data/lib/booth/syntaxes/scope.rb +21 -0
- data/lib/booth/syntaxes/scope_comparison.rb +28 -0
- data/lib/booth/syntaxes/secret_key.rb +64 -0
- data/lib/booth/syntaxes/username.rb +85 -0
- data/lib/booth/syntaxes/uuid.rb +23 -0
- data/lib/booth/test/helpers.rb +63 -0
- data/lib/booth/test/support/assert_all_partials_were_covered.rb +63 -0
- data/lib/booth/test/support/assert_logged_in.rb +49 -0
- data/lib/booth/test/support/assert_logged_out.rb +30 -0
- data/lib/booth/test/support/assert_partial.rb +29 -0
- data/lib/booth/test/support/force_login.rb +26 -0
- data/lib/booth/test/support/get_session_value.rb +35 -0
- data/lib/booth/test/support/otp_code_from_session.rb +30 -0
- data/lib/booth/test/support/soft_reset_session.rb +22 -0
- data/lib/booth/test/userland/logins/missing_authenticators.rb +72 -0
- data/lib/booth/test/userland/logins/missing_onboarding.rb +35 -0
- data/lib/booth/test/userland/logins/username_and_password.rb +40 -0
- data/lib/booth/test/userland/logins/username_and_webauth.rb +75 -0
- data/lib/booth/test/userland/logins/username_password_and_otp.rb +45 -0
- data/lib/booth/test/userland/logins/username_password_and_webauth.rb +86 -0
- data/lib/booth/test/userland/onboardings/already_logged_in.rb +64 -0
- data/lib/booth/test/userland/onboardings/otp.rb +63 -0
- data/lib/booth/test/userland/onboardings/password.rb +49 -0
- data/lib/booth/test/userland/onboardings/timeout.rb +47 -0
- data/lib/booth/test/userland/otps/manage.rb +86 -0
- data/lib/booth/test/userland/password_resets/reset.rb +102 -0
- data/lib/booth/test/userland.rb +38 -0
- data/lib/booth/test/webauthn/disable.rb +17 -0
- data/lib/booth/test/webauthn/enable.rb +19 -0
- data/lib/booth/test/webauthn/virtual_authenticators/create.rb +38 -0
- data/lib/booth/test/webauthn/virtual_authenticators/destroy.rb +20 -0
- data/lib/booth/test.rb +53 -0
- data/lib/booth/to_struct.rb +11 -0
- data/lib/booth/userland/extract_flash_messages.rb +35 -0
- data/lib/booth/userland/logins/create.rb +28 -0
- data/lib/booth/userland/logins/destroy.rb +37 -0
- data/lib/booth/userland/logins/new.rb +70 -0
- data/lib/booth/userland/logins/transitions/create/choose_username.rb +41 -0
- data/lib/booth/userland/logins/transitions/create/enter_otp.rb +70 -0
- data/lib/booth/userland/logins/transitions/create/skip_remotes.rb +24 -0
- data/lib/booth/userland/logins/transitions/create/verify_password.rb +70 -0
- data/lib/booth/userland/logins/transitions/create/webauth_authentication_initiation.rb +55 -0
- data/lib/booth/userland/logins/transitions/create/webauth_authentication_verification.rb +80 -0
- data/lib/booth/userland/logins/transitions/new/already_logged_in.rb +21 -0
- data/lib/booth/userland/logins/transitions/new/fallible.rb +27 -0
- data/lib/booth/userland/logins/transitions/new/mode_first_time.rb +20 -0
- data/lib/booth/userland/logins/transitions/new/mode_username_and_password.rb +20 -0
- data/lib/booth/userland/logins/transitions/new/mode_username_and_webauth.rb +26 -0
- data/lib/booth/userland/logins/transitions/new/mode_username_password_and_otp.rb +24 -0
- data/lib/booth/userland/logins/transitions/new/mode_username_password_and_webauth.rb +24 -0
- data/lib/booth/userland/logins/transitions/new/no_username_chosen.rb +19 -0
- data/lib/booth/userland/logins/transitions/new/remote_session_available.rb +52 -0
- data/lib/booth/userland/logins/transitions/new/timed_out.rb +25 -0
- data/lib/booth/userland/onboardings/show.rb +74 -0
- data/lib/booth/userland/onboardings/transitions/update/choose_mode.rb +58 -0
- data/lib/booth/userland/onboardings/transitions/update/choose_password.rb +41 -0
- data/lib/booth/userland/onboardings/transitions/update/choose_webauth_nickname.rb +50 -0
- data/lib/booth/userland/onboardings/transitions/update/confirm_otp.rb +58 -0
- data/lib/booth/userland/onboardings/transitions/update/confirm_password.rb +49 -0
- data/lib/booth/userland/onboardings/transitions/update/register_otp.rb +31 -0
- data/lib/booth/userland/onboardings/transitions/update/reset_otp.rb +40 -0
- data/lib/booth/userland/onboardings/transitions/update/reset_password.rb +35 -0
- data/lib/booth/userland/onboardings/transitions/update/reset_webauth.rb +46 -0
- data/lib/booth/userland/onboardings/transitions/update/webauth_authentication_initiation.rb +40 -0
- data/lib/booth/userland/onboardings/transitions/update/webauth_authentication_verification.rb +59 -0
- data/lib/booth/userland/onboardings/transitions/update/webauth_registration_initiation.rb +46 -0
- data/lib/booth/userland/onboardings/transitions/update/webauth_registration_verification.rb +56 -0
- data/lib/booth/userland/onboardings/update.rb +68 -0
- data/lib/booth/userland/otps/destroy.rb +42 -0
- data/lib/booth/userland/otps/edit.rb +72 -0
- data/lib/booth/userland/otps/guards/manageable.rb +21 -0
- data/lib/booth/userland/otps/guards/sudo.rb +23 -0
- data/lib/booth/userland/otps/show.rb +36 -0
- data/lib/booth/userland/otps/sudo.rb +51 -0
- data/lib/booth/userland/otps/transitions/update/confirm.rb +84 -0
- data/lib/booth/userland/otps/transitions/update/register.rb +40 -0
- data/lib/booth/userland/otps/transitions/update/reset.rb +31 -0
- data/lib/booth/userland/otps/update.rb +34 -0
- data/lib/booth/userland/password_resets/create.rb +73 -0
- data/lib/booth/userland/password_resets/guards/logged_out.rb +21 -0
- data/lib/booth/userland/password_resets/new.rb +57 -0
- data/lib/booth/userland/password_resets/show.rb +77 -0
- data/lib/booth/userland/password_resets/transitions/update/choose_password.rb +48 -0
- data/lib/booth/userland/password_resets/transitions/update/confirm_password.rb +54 -0
- data/lib/booth/userland/password_resets/transitions/update/reset_password.rb +29 -0
- data/lib/booth/userland/password_resets/update.rb +65 -0
- data/lib/booth/userland/passwords/destroy.rb +41 -0
- data/lib/booth/userland/passwords/edit.rb +54 -0
- data/lib/booth/userland/passwords/guards/manageable.rb +21 -0
- data/lib/booth/userland/passwords/guards/removable.rb +21 -0
- data/lib/booth/userland/passwords/guards/sudo.rb +21 -0
- data/lib/booth/userland/passwords/remove.rb +34 -0
- data/lib/booth/userland/passwords/show.rb +32 -0
- data/lib/booth/userland/passwords/sudo.rb +55 -0
- data/lib/booth/userland/passwords/transitions/remove/step.rb +27 -0
- data/lib/booth/userland/passwords/transitions/update/choose_password.rb +62 -0
- data/lib/booth/userland/passwords/transitions/update/confirm_password.rb +82 -0
- data/lib/booth/userland/passwords/update.rb +33 -0
- data/lib/booth/userland/personal_contests/show.rb +60 -0
- data/lib/booth/userland/personal_contests/update.rb +37 -0
- data/lib/booth/userland/recoveries/create.rb +48 -0
- data/lib/booth/userland/recoveries/new.rb +35 -0
- data/lib/booth/userland/registrations/create.rb +56 -0
- data/lib/booth/userland/registrations/new.rb +39 -0
- data/lib/booth/userland/sessions/destroy_one_or_other.rb +41 -0
- data/lib/booth/userland/sessions/index.rb +27 -0
- data/lib/booth/userland/sessions/show.rb +31 -0
- data/lib/booth/userland/sessions/transitions/destroy/enter_password.rb +50 -0
- data/lib/booth/userland/sessions/transitions/destroy/enter_webauth.rb +56 -0
- data/lib/booth/userland/sessions/transitions/destroy/verify_password.rb +83 -0
- data/lib/booth/userland/sessions/transitions/destroy/webauth_authentication_initiation.rb +38 -0
- data/lib/booth/userland/sessions/transitions/destroy/webauth_authentication_verification.rb +61 -0
- data/lib/booth/userland/sessions/transitions/show/enter_webauth.rb +56 -0
- data/lib/booth/userland/webauths/create.rb +83 -0
- data/lib/booth/userland/webauths/destroy.rb +60 -0
- data/lib/booth/userland/webauths/guards/manageable.rb +21 -0
- data/lib/booth/userland/webauths/guards/sudo.rb +22 -0
- data/lib/booth/userland/webauths/index.rb +43 -0
- data/lib/booth/userland/webauths/new.rb +70 -0
- data/lib/booth/userland/webauths/sudo.rb +25 -0
- data/lib/booth/userland/webauths/transitions/create/authentication_initiation.rb +52 -0
- data/lib/booth/userland/webauths/transitions/create/authentication_verification.rb +64 -0
- data/lib/booth/userland/webauths/transitions/create/choose_nickname.rb +50 -0
- data/lib/booth/userland/webauths/transitions/create/registration_initiation.rb +61 -0
- data/lib/booth/userland/webauths/transitions/create/registration_verification.rb +68 -0
- data/lib/booth/userland/webauths/transitions/create/reset.rb +36 -0
- data/lib/booth/userland/webauths/transitions/new/step.rb +23 -0
- data/lib/booth/userland/webauths/transitions/sudo/authentication_initiation.rb +47 -0
- data/lib/booth/userland/webauths/transitions/sudo/authentication_verification.rb +34 -0
- data/lib/booth/userland.rb +192 -0
- data/lib/booth/version.rb +3 -0
- data/lib/booth/webauth/authentication_verification.rb +68 -0
- data/lib/booth/webauth/demand_user_verification.rb +29 -0
- data/lib/booth/webauth/options_for_create.rb +46 -0
- data/lib/booth/webauth/options_for_get.rb +29 -0
- data/lib/booth.rb +267 -0
- data/lib/generators/booth/migration/migration_generator.rb +25 -0
- data/lib/generators/booth/migration/templates/add_credential_to_users.erb +18 -0
- data/lib/generators/booth/migration/templates/create_booth_mode_types.erb +20 -0
- data/lib/generators/booth/migration/templates/create_booth_tables.erb +135 -0
- metadata +861 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module PasswordResets
|
|
3
|
+
class Create
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
option :credential
|
|
8
|
+
option :email
|
|
9
|
+
option :ip
|
|
10
|
+
option :agent
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
do_check_applicability
|
|
14
|
+
.on_success { do_check_email_syntax }
|
|
15
|
+
.on_success { do_check_cooldown }
|
|
16
|
+
.on_success { do_create_password_reset }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def do_check_applicability
|
|
22
|
+
return Tron.success :credential_has_password if credential.applicable_for_password_reset?
|
|
23
|
+
|
|
24
|
+
debug { 'This credential has no password to be reset' }
|
|
25
|
+
Tron.failure :credential_cannot_reset_password, public_message: I18n.t('booth.password_reset_not_available')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def do_check_email_syntax
|
|
29
|
+
check = ::Booth::Syntaxes::Email.call(email)
|
|
30
|
+
|
|
31
|
+
check.on_success do
|
|
32
|
+
@email_address = check.normalized_email
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
check
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def do_check_cooldown
|
|
39
|
+
::Booth::Cooldowns::PasswordReset.call(credential:)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def do_create_password_reset
|
|
43
|
+
password_reset = nil
|
|
44
|
+
|
|
45
|
+
::Booth::Models::PasswordReset.transaction do
|
|
46
|
+
password_reset = ::Booth::Models::PasswordReset.create! credential:, creator_ip: ip
|
|
47
|
+
::Booth::Audits::Register::RequestedPasswordReset.call credential:, ip:, agent:
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
Tron.success :applicable_for_reset, username: credential.username,
|
|
51
|
+
email: @email_address,
|
|
52
|
+
credential_id: credential.id,
|
|
53
|
+
secret_key: password_reset.secret_key
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module PasswordResets
|
|
3
|
+
class Find
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
option :secret_key
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
do_check_secret_key_syntax
|
|
11
|
+
.on_success { do_find_password_reset }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def do_check_secret_key_syntax
|
|
17
|
+
::Booth::Syntaxes::SecretKey.call(secret_key)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def do_find_password_reset
|
|
21
|
+
debug { "Looking for PasswordReset with secret key #{secret_key.inspect}" }
|
|
22
|
+
@password_reset = ::Booth::Models::PasswordReset.find_by(secret_key:)
|
|
23
|
+
|
|
24
|
+
if @password_reset
|
|
25
|
+
debug { "Found PasswordReset with ID #{@password_reset.id.inspect}" }
|
|
26
|
+
@password_reset.update! accessed_at: Time.current if @password_reset.accessed_at.blank?
|
|
27
|
+
return Tron.success(:found_password_reset, password_reset: @password_reset)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
message = "Could not find userland PasswordReset with secret key #{secret_key.inspect}"
|
|
31
|
+
debug { message }
|
|
32
|
+
Tron.failure :password_reset_not_found, public_message: I18n.t('booth.unknown_secret_key')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module PasswordResets
|
|
3
|
+
class PropagateToCredential
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :password_reset
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
debug { 'Propagating PasswordReset to Credential...' }
|
|
11
|
+
raise "Expected PasswordReset to be valid: #{password_reset.errors.to_a.to_sentence}" if password_reset.invalid?
|
|
12
|
+
|
|
13
|
+
password_reset.transaction do
|
|
14
|
+
update_credential!
|
|
15
|
+
invalidate_password_resets!
|
|
16
|
+
finalize_password_reset!
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def update_credential!
|
|
23
|
+
password_reset.credential.update! password_digest: password_reset.password_digest
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def invalidate_password_resets!
|
|
27
|
+
password_reset.other_password_resets_of_this_credential
|
|
28
|
+
.update_all(revoked_at: Time.current) # rubocop:disable Rails/SkipsModelValidations
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def finalize_password_reset!
|
|
32
|
+
password_reset.update! propagated_at: Time.current
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module PasswordResets
|
|
3
|
+
class Step
|
|
4
|
+
include ::Booth::MethodObject
|
|
5
|
+
|
|
6
|
+
param :password_reset
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
return :completed if password_reset.completed?
|
|
10
|
+
return :revoked if password_reset.revoked?
|
|
11
|
+
return :timed_out unless password_reset.recently_created?
|
|
12
|
+
return :confirm_password if password_reset.password_chosen_at.present?
|
|
13
|
+
|
|
14
|
+
:choose_password
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Recoveries
|
|
3
|
+
class Create
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
option :scope
|
|
8
|
+
option :email
|
|
9
|
+
option :ip
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
do_check_cooldown
|
|
13
|
+
.on_success { do_check_email_syntax }
|
|
14
|
+
.on_success { do_create_recovery }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def do_check_cooldown
|
|
20
|
+
# TODO: Check recovery cooldown of email in the table `booth_recoveries`.
|
|
21
|
+
# Respect consumed_at and revoked_at and created_at and creator_ip and email.
|
|
22
|
+
Tron.success :username_rate_limit_ok_dummy
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def do_check_email_syntax
|
|
26
|
+
check = ::Booth::Syntaxes::Email.call(email)
|
|
27
|
+
|
|
28
|
+
check.on_success do
|
|
29
|
+
@email_address = check.normalized_email
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
check
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def do_create_recovery
|
|
36
|
+
recovery = ::Booth::Models::Recovery.create! scope: scope,
|
|
37
|
+
email: email,
|
|
38
|
+
creator_ip: ip
|
|
39
|
+
|
|
40
|
+
Tron.success :applicable_for_recovery, email: recovery.email,
|
|
41
|
+
recovery_id: recovery.id
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
# Convenience wrapper for `Rack::Request`.
|
|
3
|
+
class Request
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
|
|
6
|
+
# Can be used as a DRY::Initializer Coercer.
|
|
7
|
+
# See https://dry-rb.org/gems/dry-initializer/master/type-constraints/#back-references
|
|
8
|
+
def self.call(request, initializer)
|
|
9
|
+
# `request` is an `ActionDispatch::Request` or a `Rack::Request`.
|
|
10
|
+
# But if it already has been coerced, just return it immediately.
|
|
11
|
+
# This makes it easier to pass the request from one MethodObject to another.
|
|
12
|
+
return request if request.is_a?(self)
|
|
13
|
+
|
|
14
|
+
# `initializer` is an instance that is trying to coerce one of its params into a `Booth::Request`.
|
|
15
|
+
# By convention, that's where we assume the scope to be specified. So we take it from there.
|
|
16
|
+
new(request:, scope: initializer.scope)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(request:, scope:)
|
|
20
|
+
request = ActionDispatch::Request.new(request.env) if request.is_a?(Rack::Request)
|
|
21
|
+
|
|
22
|
+
@request = request
|
|
23
|
+
@scope = ::Booth::Syntaxes::Scope.call(scope).normalized_scope
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr_reader :scope
|
|
27
|
+
|
|
28
|
+
def agent
|
|
29
|
+
::Booth::Requests::Agent.call(request:)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ip
|
|
33
|
+
::Booth::Requests::Ip.call(request:)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def location
|
|
37
|
+
::Booth::Geolocation.lookup(ip)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def authentication
|
|
41
|
+
::Booth::Requests::Authentication.new(scope:, warden: request.env['warden'])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def session(namespace:)
|
|
45
|
+
::Booth::Requests::Session.new(scope:, namespace:, session: request.session)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def sudo
|
|
49
|
+
::Booth::Requests::Sudo.new(scope:, request: self)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def storage
|
|
53
|
+
::Booth::Requests::Storage.new(scope:, request: self)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def params
|
|
57
|
+
# Huh, I thought the following line was needed:
|
|
58
|
+
# return request.param if request.params.is_a?(::ActionController::Parameters)
|
|
59
|
+
|
|
60
|
+
@params ||= ::ActionController::Parameters.new(request.params)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def return_path
|
|
64
|
+
::Booth::Requests::ReturnPath.call(params:)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# ------------
|
|
68
|
+
# Requirements
|
|
69
|
+
# ------------
|
|
70
|
+
|
|
71
|
+
def must_be_logged_in!
|
|
72
|
+
return if authentication.logged_in?
|
|
73
|
+
|
|
74
|
+
debug { "Expected someone to be logged in in scope #{scope.inspect}" }
|
|
75
|
+
raise ::Booth::Errors::NotAuthenticated
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def must_be_html!
|
|
79
|
+
request.format.html? || raise(::Booth::Errors::MustBeHtml)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def must_be_json!
|
|
83
|
+
request.format.json? || raise(::Booth::Errors::MustBeJson)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def must_be_get!
|
|
87
|
+
request.get? || raise(::Booth::Errors::MustBeGet)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def must_be_post!
|
|
91
|
+
request.post? || raise(::Booth::Errors::MustBePost)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def must_be_patch!
|
|
95
|
+
request.patch? || raise(::Booth::Errors::MustBePatch)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def must_be_delete!
|
|
99
|
+
request.delete? || raise(::Booth::Errors::MustBeDelete)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
attr_reader :request
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Requests
|
|
3
|
+
class Agent
|
|
4
|
+
include ::Booth::MethodObject
|
|
5
|
+
|
|
6
|
+
option :request
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
# Truncate to prevent potential cookie overflows and DB bloating.
|
|
10
|
+
request.user_agent.to_s.truncate(255).presence
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Requests
|
|
3
|
+
# Convenience wrapper for `Warden::Manager`.
|
|
4
|
+
class Authentication
|
|
5
|
+
delegate :username, :credential_id, :mode,
|
|
6
|
+
to: :passport,
|
|
7
|
+
allow_nil: true
|
|
8
|
+
|
|
9
|
+
def initialize(scope:, warden:)
|
|
10
|
+
@scope = scope
|
|
11
|
+
@warden = warden
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def login(session:)
|
|
15
|
+
# Whatever instance we pass in to Warden,
|
|
16
|
+
# it needs to be that, which we also want do get out.
|
|
17
|
+
# Because serialization only takes place for the *next* request.
|
|
18
|
+
# In the *same* request, whatever we pass in, is returned back as it is.
|
|
19
|
+
warden.set_user ::Booth::Sessions::ToPassport.call(session), scope:
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def logged_in?
|
|
23
|
+
warden.authenticated?(scope)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def logged_in_as?(credential:)
|
|
27
|
+
logged_in? && credential.id == credential_id
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def logout
|
|
31
|
+
warden.logout(scope)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def session_id
|
|
35
|
+
passport&.id
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
attr_reader :scope, :warden
|
|
41
|
+
|
|
42
|
+
def passport
|
|
43
|
+
warden.user(scope)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Requests
|
|
3
|
+
class Ip
|
|
4
|
+
include ::Booth::MethodObject
|
|
5
|
+
|
|
6
|
+
option :request
|
|
7
|
+
option :raise_if_invalid, default: -> { true }
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
check = ::Booth::Syntaxes::Ip.call(raw_ip)
|
|
11
|
+
check.on_success { return check.normalized_ip }
|
|
12
|
+
|
|
13
|
+
raise ArgumentError "Invalid IP: #{raw_ip.inspect}" if raise_if_invalid
|
|
14
|
+
|
|
15
|
+
check
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def raw_ip
|
|
21
|
+
# One could also use this:
|
|
22
|
+
# request.env['action_dispatch.remote_ip'].to_s
|
|
23
|
+
# But I think the `Rack::Request#trusted_proxy?` feature makes the following usable enough:
|
|
24
|
+
request.ip
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Requests
|
|
3
|
+
class ReturnPath
|
|
4
|
+
include ::Booth::MethodObject
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
option :params
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
uri = ::URI.parse raw_return_path
|
|
11
|
+
|
|
12
|
+
# Make sure we do not redirect to (external) URLs but only paths (i.e. protocol and domain-local).
|
|
13
|
+
result = [uri.path, uri.query].compact.join '?'
|
|
14
|
+
|
|
15
|
+
# We usually never store the return path in a cookie, but some developer might still try it.
|
|
16
|
+
# To avoid potential cookie overflows, allow only as many characters as reasonably needed.
|
|
17
|
+
result = result[0..1024]
|
|
18
|
+
return if result.blank?
|
|
19
|
+
|
|
20
|
+
# We always assume it is a full path and fix any missing beginning slash.
|
|
21
|
+
result.starts_with?('/') ? result : result.prepend('/')
|
|
22
|
+
rescue URI::InvalidURIError
|
|
23
|
+
debug { "Invalid return path: #{raw_return_path.inspect}" } if raw_return_path
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def raw_return_path
|
|
30
|
+
params.permit![:return_path]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Requests
|
|
3
|
+
# Convenience wrapper for `ActionDispatch::Session::CookieStore`.
|
|
4
|
+
class Session
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
attr_reader :scope
|
|
8
|
+
|
|
9
|
+
def initialize(scope:, namespace:, session:)
|
|
10
|
+
@scope = ::Booth::Syntaxes::Scope.call(scope).normalized_scope
|
|
11
|
+
@namespace = namespace
|
|
12
|
+
@session = session
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# -------------------
|
|
16
|
+
# Getters and Setters
|
|
17
|
+
# -------------------
|
|
18
|
+
|
|
19
|
+
def [](key)
|
|
20
|
+
return if timed_out?
|
|
21
|
+
raise ArgumentError if key.to_s == 'created_at'
|
|
22
|
+
|
|
23
|
+
reset_if_too_old!
|
|
24
|
+
session[path(key)]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def []=(key, value)
|
|
28
|
+
return if timed_out?
|
|
29
|
+
raise ArgumentError if key.to_s == 'created_at'
|
|
30
|
+
|
|
31
|
+
ensure_ticking
|
|
32
|
+
reset_if_too_old!
|
|
33
|
+
session[path(key)] = value
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def delete(key)
|
|
37
|
+
raise ArgumentError if key.to_s == 'created_at'
|
|
38
|
+
|
|
39
|
+
reset_if_too_old!
|
|
40
|
+
session.delete path(key)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def reset
|
|
44
|
+
debug { "Resetting #{namespace.inspect} cookie data in scope #{scope.inspect}" }
|
|
45
|
+
|
|
46
|
+
# There is no `#delete_if` in a `ActionDispatch::Request::Session`.
|
|
47
|
+
keys_to_delete = session.keys.select { _1.to_s.start_with?(path(nil)) }
|
|
48
|
+
keys_to_delete.each { session.delete(_1) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# -----
|
|
52
|
+
# Timer
|
|
53
|
+
# -----
|
|
54
|
+
|
|
55
|
+
def lifespan
|
|
56
|
+
::Booth.config.interaction_timeout.to_i
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def seconds_until_auto_reset
|
|
60
|
+
return 0 if @reset_due_to_timeout
|
|
61
|
+
|
|
62
|
+
lifespan - age
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def timed_out?
|
|
66
|
+
reset_if_too_old!
|
|
67
|
+
|
|
68
|
+
!!@reset_due_to_timeout
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
attr_reader :namespace, :session
|
|
74
|
+
|
|
75
|
+
def ensure_ticking
|
|
76
|
+
session[path(:created_at)] ||= Time.current.to_i
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def ticking?
|
|
80
|
+
!!session[path(:created_at)]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def age
|
|
84
|
+
return 0 unless ticking?
|
|
85
|
+
|
|
86
|
+
Time.current.to_i - session[path(:created_at)]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def reset_if_too_old!
|
|
90
|
+
return if seconds_until_auto_reset.positive?
|
|
91
|
+
|
|
92
|
+
debug do
|
|
93
|
+
"Timeout-resetting #{namespace.inspect} cookie data in scope #{scope.inspect} " \
|
|
94
|
+
"because it is older than #{lifespan} seconds"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
@reset_due_to_timeout = true
|
|
98
|
+
reset
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def path(key)
|
|
102
|
+
[:booth, scope, namespace, key].join('.')
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Requests
|
|
3
|
+
class Storage
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
|
|
6
|
+
def initialize(scope:, request:)
|
|
7
|
+
@scope = scope
|
|
8
|
+
@request = request
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def login
|
|
12
|
+
@login ||= ::Booth::Requests::Storages::Login.new(
|
|
13
|
+
session: request.session(namespace: :login)
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def otp
|
|
18
|
+
@otp ||= ::Booth::Requests::Storages::Otp.new(
|
|
19
|
+
session: request.session(namespace: :otp)
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def webauth
|
|
24
|
+
@webauth ||= ::Booth::Requests::Storages::Webauth.new(
|
|
25
|
+
session: request.session(namespace: :webauth)
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def password
|
|
30
|
+
@password ||= ::Booth::Requests::Storages::Password.new(
|
|
31
|
+
session: request.session(namespace: :password)
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def password_reset
|
|
36
|
+
@password_reset ||= ::Booth::Requests::Storages::PasswordReset.new(
|
|
37
|
+
session: request.session(namespace: :password_reset)
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def recovery
|
|
42
|
+
@recovery ||= ::Booth::Requests::Storages::Recovery.new(
|
|
43
|
+
session: request.session(namespace: :recovery)
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def registration
|
|
48
|
+
@registration ||= ::Booth::Requests::Storages::Registration.new(
|
|
49
|
+
session: request.session(namespace: :registration)
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
attr_reader :scope, :request
|
|
56
|
+
|
|
57
|
+
def session
|
|
58
|
+
request.session(namespace: :sudo)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Requests
|
|
3
|
+
module Storages
|
|
4
|
+
class Login
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
delegate :reset, :timed_out?, :lifespan, :seconds_until_auto_reset, to: :session
|
|
8
|
+
|
|
9
|
+
def initialize(session:)
|
|
10
|
+
@session = session
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# -------
|
|
14
|
+
# Getters
|
|
15
|
+
# -------
|
|
16
|
+
|
|
17
|
+
def username
|
|
18
|
+
session[:username]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def credential_for_username
|
|
22
|
+
return @credential_for_username if defined?(@credential_for_username)
|
|
23
|
+
|
|
24
|
+
@credential_for_username = ::Booth::Models::Credential.find_by(id: session[:credential_for_username])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def contest_for_username
|
|
28
|
+
return @contest_for_username if defined?(@contest_for_username)
|
|
29
|
+
|
|
30
|
+
@contest_for_username = ::Booth::Models::Contest.find_by(id: session[:contest_for_username])
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def password_authenticated_credential
|
|
34
|
+
return @password_authenticated_credential if defined?(@password_authenticated_credential)
|
|
35
|
+
|
|
36
|
+
@password_authenticated_credential = ::Booth::Models::Credential.find_by(
|
|
37
|
+
id: session[:password_authenticated_credential]
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def webauthn_challenge
|
|
42
|
+
session[:webauthn_challenge].presence
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def skip_remotes?
|
|
46
|
+
session[:skip_remotes].present?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# -------
|
|
50
|
+
# Setters
|
|
51
|
+
# -------
|
|
52
|
+
|
|
53
|
+
def username=(new_username)
|
|
54
|
+
session[:username] = new_username
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def credential_for_username=(new_credential)
|
|
58
|
+
debug { "Persisting credential for username in browser session for scope #{scope.inspect}" }
|
|
59
|
+
session[:credential_for_username] = new_credential.id
|
|
60
|
+
@credential_for_username = nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def contest_for_username=(new_contest)
|
|
64
|
+
debug { "Persisting contest for username in browser session for scope #{scope.inspect}" }
|
|
65
|
+
session[:contest_for_username] = new_contest.id
|
|
66
|
+
@contest_for_username = nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def reset_credential_for_username
|
|
70
|
+
debug { "Resetting credential in browser session for scope #{scope.inspect}" }
|
|
71
|
+
session.delete(:credential_for_username)
|
|
72
|
+
@credential_for_username = nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def password_authenticated_credential=(new_credential)
|
|
76
|
+
debug { "Persisting password authenticated credential in browser session for scope #{scope.inspect}" }
|
|
77
|
+
session[:password_authenticated_credential] = new_credential.id
|
|
78
|
+
@password_authenticated_credential = nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def webauthn_challenge=(new_challenge)
|
|
82
|
+
if new_challenge
|
|
83
|
+
debug do
|
|
84
|
+
"Persisting webauth challenge #{new_challenge.inspect} in browser session for scope #{scope.inspect}"
|
|
85
|
+
end
|
|
86
|
+
else
|
|
87
|
+
debug { "Removing webauth challenge from browser session for scope #{scope.inspect}" }
|
|
88
|
+
end
|
|
89
|
+
session[:webauthn_challenge] = new_challenge.presence
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def skip_remotes
|
|
93
|
+
session[:skip_remotes] = true
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def reset_remotes
|
|
97
|
+
session.delete(:skip_remotes)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
attr_reader :session
|
|
103
|
+
|
|
104
|
+
delegate :scope, to: :session, private: true
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|