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,58 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Syntaxes
|
|
3
|
+
class ContestCode
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :input
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
debug { "Checking contest #{input.inspect} for valid syntax..." }
|
|
11
|
+
check_blank.on_success { check_characters }
|
|
12
|
+
.on_success { check_length }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def check_blank
|
|
18
|
+
return Tron.success :contest_code_present if input.present?
|
|
19
|
+
|
|
20
|
+
debug { 'This contest is blank.' }
|
|
21
|
+
Tron.failure :blank_contest_code,
|
|
22
|
+
normalized_contest_code: nil,
|
|
23
|
+
public_message: I18n.t('booth.blank_contest_code')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def check_characters
|
|
27
|
+
return Tron.success :contest_code_consists_of_digits if input_without_spaces.match?(allowed_regexp)
|
|
28
|
+
|
|
29
|
+
debug { 'This contest contains invalid characters' }
|
|
30
|
+
Tron.failure :invalid_contest_code_format,
|
|
31
|
+
normalized_contest_code: nil,
|
|
32
|
+
public_message: I18n.t('booth.invalid_contest_code_format')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def check_length
|
|
36
|
+
if input_without_spaces.to_s.length == ::Booth.config.contest_digits
|
|
37
|
+
return Tron.success :valid_contest_code_syntax,
|
|
38
|
+
normalized_contest_code: input_without_spaces
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
debug { "This contest is not #{::Booth.config.contest_digits} characters long." }
|
|
42
|
+
Tron.failure :wrong_contest_code_length,
|
|
43
|
+
normalized_contest_code: nil,
|
|
44
|
+
public_message: I18n.t('booth.wrong_contest_code_length', digits: ::Booth.config.contest_digits)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Helpers
|
|
48
|
+
|
|
49
|
+
def input_without_spaces
|
|
50
|
+
input.to_s.delete(' ')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def allowed_regexp
|
|
54
|
+
/\A\d+\z/
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Syntaxes
|
|
3
|
+
class Email
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :input
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
debug { "Checking email #{input.inspect} for valid syntax..." }
|
|
11
|
+
check_blank.on_success { check_too_short }
|
|
12
|
+
.on_success { check_too_long }
|
|
13
|
+
.on_success { check_characters }
|
|
14
|
+
.on_success { check_ampersand }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def check_blank
|
|
20
|
+
return Tron.success :email_present if input.present?
|
|
21
|
+
|
|
22
|
+
debug { 'This email is blank.' }
|
|
23
|
+
Tron.failure :blank_email,
|
|
24
|
+
normalized_email: nil,
|
|
25
|
+
normalized_invalid_email: normalized_email,
|
|
26
|
+
public_message: I18n.t('booth.blank_email')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def check_too_short
|
|
30
|
+
return Tron.success :email_not_too_short if input.to_s.length >= min_length
|
|
31
|
+
|
|
32
|
+
debug { "This email is less than #{min_length} characters long." }
|
|
33
|
+
Tron.failure :email_is_too_short,
|
|
34
|
+
normalized_email: nil,
|
|
35
|
+
normalized_invalid_email: normalized_email,
|
|
36
|
+
public_message: I18n.t('booth.email_too_short', minimum: min_length)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def check_too_long
|
|
40
|
+
return Tron.success :email_not_too_long if input.to_s.length <= max_length
|
|
41
|
+
|
|
42
|
+
debug { "This email is more than #{max_length} characters long." }
|
|
43
|
+
Tron.failure :email_is_too_long,
|
|
44
|
+
normalized_email: nil,
|
|
45
|
+
normalized_invalid_email: normalized_email,
|
|
46
|
+
public_message: I18n.t('booth.email_too_long', maximum: max_length)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def check_characters
|
|
50
|
+
return Tron.success :all_characters_valid if input.to_s.length == normalized_email.length
|
|
51
|
+
|
|
52
|
+
debug { 'This email contains invalid characters' }
|
|
53
|
+
Tron.failure :invalid_email_format,
|
|
54
|
+
normalized_email: nil,
|
|
55
|
+
normalized_invalid_email: normalized_email,
|
|
56
|
+
public_message: I18n.t('booth.invalid_email_characters')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# See https://github.com/heartcombo/devise/blob/8593801130f2df94a50863b5db535c272b00efe1/lib/devise.rb#L112-L116
|
|
60
|
+
def check_ampersand
|
|
61
|
+
if input.to_s.match(/\A[^@]+@[^@]+\z/)
|
|
62
|
+
debug { 'This email has the correct syntax' }
|
|
63
|
+
return Tron.success :valid_email_syntax,
|
|
64
|
+
normalized_email:,
|
|
65
|
+
normalized_invalid_email: normalized_email
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
debug { 'This email does not contain an ampersand' }
|
|
69
|
+
Tron.failure :email_ampersand_invalid,
|
|
70
|
+
normalized_email: nil,
|
|
71
|
+
normalized_invalid_email: normalized_email,
|
|
72
|
+
public_message: I18n.t('booth.invalid_email_format')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Limit size to prevent cookie overflows.
|
|
76
|
+
def normalized_email
|
|
77
|
+
input.to_s
|
|
78
|
+
.downcase
|
|
79
|
+
.delete("^#{allowed_characters}")
|
|
80
|
+
.to(max_length - 1)
|
|
81
|
+
.presence
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def allowed_characters
|
|
85
|
+
::Regexp.escape 'abcdefghijklmnopqrstuvwxyz0123456789@ßöäü_+-,.!?#$%'
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def min_length
|
|
89
|
+
3
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def max_length
|
|
93
|
+
254
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Syntaxes
|
|
3
|
+
class Ip
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :input
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
# debug { "Checking IP address #{input.inspect} for valid syntax..." }
|
|
11
|
+
check_blank.on_success { check_validity }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def check_blank
|
|
17
|
+
return Tron.success :ip_present if input.present?
|
|
18
|
+
|
|
19
|
+
# debug { 'This IP is blank.' }
|
|
20
|
+
Tron.failure :blank_ip, normalized_ip: nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def check_validity
|
|
24
|
+
return Tron.success :ip_valid, normalized_ip: ip_addr.to_s if ip_addr
|
|
25
|
+
|
|
26
|
+
# debug { 'This IP is invalid.' }
|
|
27
|
+
Tron.failure :invalid_ip, normalized_ip: nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def ip_addr
|
|
31
|
+
IPAddr.new(input)
|
|
32
|
+
rescue ArgumentError
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Syntaxes
|
|
3
|
+
class Otp
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :input
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
check_blank.on_success { check_characters }
|
|
11
|
+
.on_success { check_length }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def check_blank
|
|
17
|
+
return Tron.success :otp_present if input.present?
|
|
18
|
+
|
|
19
|
+
debug { "OTP #{input.inspect} is blank." }
|
|
20
|
+
Tron.failure :blank_otp,
|
|
21
|
+
normalized_otp: nil,
|
|
22
|
+
public_message: I18n.t('booth.blank_otp')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def check_characters
|
|
26
|
+
return Tron.success :otp_consists_of_digits if input_without_spaces.match?(allowed_regexp)
|
|
27
|
+
|
|
28
|
+
debug { "OTP #{input.inspect} contains invalid characters" }
|
|
29
|
+
Tron.failure :invalid_otp_format,
|
|
30
|
+
normalized_otp: nil,
|
|
31
|
+
public_message: I18n.t('booth.invalid_otp_format')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def check_length
|
|
35
|
+
if input_without_spaces.to_s.length == ::Booth.config.otp_digits
|
|
36
|
+
return Tron.success :valid_otp_syntax,
|
|
37
|
+
normalized_otp: input_without_spaces
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
debug { "OTP #{input.inspect} is not #{::Booth.config.otp_digits} characters long." }
|
|
41
|
+
Tron.failure :wrong_otp_length,
|
|
42
|
+
normalized_otp: nil,
|
|
43
|
+
public_message: I18n.t('booth.wrong_otp_length', digits: ::Booth.config.otp_digits)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Helpers
|
|
47
|
+
|
|
48
|
+
def input_without_spaces
|
|
49
|
+
input.to_s.delete(' ')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def allowed_regexp
|
|
53
|
+
/\A\d+\z/
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Syntaxes
|
|
3
|
+
class Scope
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :input
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
return Tron.success(:valid_scope_syntax, normalized_scope: input.to_sym) if regexp.match(input.to_s)
|
|
11
|
+
|
|
12
|
+
raise ::Booth::Errors::InvalidScopeSyntax, input
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Same convention as a Ruby variable name.
|
|
16
|
+
def regexp
|
|
17
|
+
/\A[a-z]{1}[a-z0-9_]+[a-z0-9]{1}\z/
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Syntaxes
|
|
3
|
+
class ScopeComparison
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
option :this, as: :raw_this
|
|
8
|
+
option :that, as: :raw_that
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
return Tron.success(:identical_scopes) if this == that
|
|
12
|
+
|
|
13
|
+
debug { "The requested scope #{this.inspect} does not match what's on record #{that.inspect}" }
|
|
14
|
+
Tron.failure :mismatching_scopes, this:, that:
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def this
|
|
20
|
+
::Booth::Syntaxes::Scope.call(raw_this).normalized_scope
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def that
|
|
24
|
+
::Booth::Syntaxes::Scope.call(raw_that).normalized_scope
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Syntaxes
|
|
3
|
+
class SecretKey
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :input
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
debug { "Checking secret key #{input.inspect} for valid syntax..." }
|
|
11
|
+
check_missing.on_success { check_blank }
|
|
12
|
+
.on_success { check_length }
|
|
13
|
+
.on_success { check_characters }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def check_missing
|
|
19
|
+
return Tron.success :secret_key_non_nil unless input.nil?
|
|
20
|
+
|
|
21
|
+
debug { 'This secret key is nil.' }
|
|
22
|
+
Tron.failure :missing_secret_key,
|
|
23
|
+
normalized_secret_key: nil,
|
|
24
|
+
public_message: I18n.t('booth.missing_secret_key')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def check_blank
|
|
28
|
+
return Tron.success :secret_key_present if input.present?
|
|
29
|
+
|
|
30
|
+
debug { 'This secret key is blank.' }
|
|
31
|
+
Tron.failure :blank_secret_key,
|
|
32
|
+
normalized_secret_key: nil,
|
|
33
|
+
public_message: I18n.t('booth.blank_secret_key')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def check_length
|
|
37
|
+
return Tron.success :secret_key_has_correct_length if input.to_s.length == 30
|
|
38
|
+
|
|
39
|
+
debug { 'This secret key is not 30 characters long.' }
|
|
40
|
+
Tron.failure :wrong_secret_key_length,
|
|
41
|
+
normalized_secret_key: nil,
|
|
42
|
+
public_message: I18n.t('booth.wrong_secret_key_length')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def check_characters
|
|
46
|
+
if input.to_s.match?(allowed_regexp)
|
|
47
|
+
return Tron.success :valid_secret_key_syntax,
|
|
48
|
+
normalized_secret_key: input.to_s
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
debug { 'This secret key contains invalid characters' }
|
|
52
|
+
Tron.failure :invalid_secret_key_format,
|
|
53
|
+
normalized_secret_key: nil,
|
|
54
|
+
public_message: I18n.t('booth.invalid_secret_key_format')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Helpers
|
|
58
|
+
|
|
59
|
+
def allowed_regexp
|
|
60
|
+
/\A[1-9a-zA-Z]{30}\z/
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Syntaxes
|
|
3
|
+
class Username
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :input
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
# debug { "Checking username #{input.inspect} for valid syntax..." }
|
|
11
|
+
check_blank.on_success { check_too_short }
|
|
12
|
+
.on_success { check_too_long }
|
|
13
|
+
.on_success { check_characters }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def check_blank
|
|
19
|
+
return Tron.success :username_present if input.present?
|
|
20
|
+
|
|
21
|
+
debug { 'This username is blank.' }
|
|
22
|
+
Tron.failure :blank_username,
|
|
23
|
+
normalized_username: nil,
|
|
24
|
+
normalized_invalid_username: normalized_username,
|
|
25
|
+
public_message: I18n.t('booth.blank_username')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def check_too_short
|
|
29
|
+
return Tron.success :username_not_too_short if input.to_s.length >= min_length
|
|
30
|
+
|
|
31
|
+
debug { "This username is less than #{min_length} characters long." }
|
|
32
|
+
Tron.failure :username_is_too_short,
|
|
33
|
+
normalized_username: nil,
|
|
34
|
+
normalized_invalid_username: normalized_username,
|
|
35
|
+
public_message: I18n.t('booth.username_too_short', minimum: min_length)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def check_too_long
|
|
39
|
+
return Tron.success :username_not_too_long if input.to_s.length <= max_length
|
|
40
|
+
|
|
41
|
+
debug { "This username is more than #{max_length} characters long." }
|
|
42
|
+
Tron.failure :username_is_too_long,
|
|
43
|
+
normalized_username: nil,
|
|
44
|
+
normalized_invalid_username: normalized_username,
|
|
45
|
+
public_message: I18n.t('booth.username_too_long', maximum: max_length)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def check_characters
|
|
49
|
+
if input.to_s.length == normalized_username.length
|
|
50
|
+
debug { 'This username has the correct syntax' }
|
|
51
|
+
return Tron.success :valid_username_syntax,
|
|
52
|
+
normalized_username:,
|
|
53
|
+
normalized_invalid_username: normalized_username
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
debug { 'This username contains invalid characters' }
|
|
57
|
+
Tron.failure :invalid_username_format,
|
|
58
|
+
normalized_username: nil,
|
|
59
|
+
normalized_invalid_username: normalized_username,
|
|
60
|
+
public_message: I18n.t('booth.invalid_username_format')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Limit size to prevent cookie overflows.
|
|
64
|
+
def normalized_username
|
|
65
|
+
input.to_s
|
|
66
|
+
.downcase
|
|
67
|
+
.delete("^#{allowed_characters}")
|
|
68
|
+
.to(max_length - 1)
|
|
69
|
+
.presence
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def allowed_characters
|
|
73
|
+
::Regexp.escape 'abcdefghijklmnopqrstuvwxyz0123456789@ßöäü_+-,.!?#$%'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def min_length
|
|
77
|
+
3
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def max_length
|
|
81
|
+
50
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Syntaxes
|
|
3
|
+
class Uuid
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :input
|
|
8
|
+
option :raise_if_invalid, default: -> { true }
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
return Tron.success(:valid_uuid, uuid: input) if regexp.match(input.to_s)
|
|
12
|
+
raise ArgumentError, "Invalid UUID: #{input.inspect}" if raise_if_invalid
|
|
13
|
+
|
|
14
|
+
Tron.failure :invalid_uuid, uuid: nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# For practical reasons we only accept downcased (i.e. normalized) UUIDs.
|
|
18
|
+
def regexp
|
|
19
|
+
/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'active_support/testing/time_helpers'
|
|
2
|
+
|
|
3
|
+
require_relative 'support/assert_all_partials_were_covered'
|
|
4
|
+
require_relative 'support/assert_logged_in'
|
|
5
|
+
require_relative 'support/assert_logged_out'
|
|
6
|
+
require_relative 'support/assert_partial'
|
|
7
|
+
require_relative 'support/get_session_value'
|
|
8
|
+
require_relative 'support/otp_code_from_session'
|
|
9
|
+
require_relative 'support/soft_reset_session'
|
|
10
|
+
require_relative 'webauthn/disable'
|
|
11
|
+
require_relative 'webauthn/enable'
|
|
12
|
+
require_relative 'webauthn/virtual_authenticators/create'
|
|
13
|
+
require_relative 'webauthn/virtual_authenticators/destroy'
|
|
14
|
+
|
|
15
|
+
module Booth
|
|
16
|
+
module Test
|
|
17
|
+
module Helpers
|
|
18
|
+
extend ActiveSupport::Concern
|
|
19
|
+
|
|
20
|
+
included do
|
|
21
|
+
include ActiveSupport::Testing::TimeHelpers
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def assert_logged_out
|
|
25
|
+
::Booth::Test::Support::AssertLoggedOut.call(page:, scope:)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def assert_logged_in(credential:)
|
|
29
|
+
::Booth::Test::Support::AssertLoggedIn.call(page:, scope:, credential:)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def assert_userland_partial(controller:, step:)
|
|
33
|
+
::Booth::Test::Support::AssertPartial.call(page:, namespace: :userland, controller:, step:)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def extract_otp_secret_key_and_generate_code
|
|
37
|
+
secret_key = page.body.split('?secret=').last.split('&').first
|
|
38
|
+
raise 'Expected an OTP secret but found none' if secret_key.blank?
|
|
39
|
+
|
|
40
|
+
code = ::Booth::Models::Credential.new(otp_secret_key: secret_key).otp_code
|
|
41
|
+
Booth.config.logger&.debug(to_s) { "Extracted OTP secret #{secret_key} and derived the code #{code}" }
|
|
42
|
+
code
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def soft_reset_session
|
|
46
|
+
::Booth::Test::Support::SoftResetSession.call(page:)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def setup_virtual_authenticator_environment
|
|
50
|
+
# For some reason, the Chrome Virtual Authenticator Environment often leaks from one test to the next.
|
|
51
|
+
# All kinds of errors in Webauth only cause one single generic `NotAllowedError` for privacy reasons
|
|
52
|
+
# So it's impossible to debug the actual cause. I just found out after many hours that disabling first, works.
|
|
53
|
+
::Booth::Test::Webauthn::Disable.call devtools: page.driver.browser.devtools
|
|
54
|
+
::Booth::Test::Webauthn::Enable.call devtools: page.driver.browser.devtools
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def create_virtual_authenticator(has_user_verification: true)
|
|
58
|
+
setup_virtual_authenticator_environment
|
|
59
|
+
::Booth::Test::Webauthn::VirtualAuthenticators::Create.call(page:, has_user_verification:)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Test
|
|
3
|
+
module Support
|
|
4
|
+
class AssertAllPartialsWereCovered
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
return if missing_partials.empty?
|
|
9
|
+
|
|
10
|
+
raise "Expected these partials to be covered: #{missing_partials}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def missing_partials
|
|
16
|
+
(expected_partials - covered_partials)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def expected_partials # rubocop:disable Metrics/MethodLength
|
|
20
|
+
%w[
|
|
21
|
+
userland/login/enter_otp
|
|
22
|
+
userland/login/enter_password
|
|
23
|
+
userland/login/enter_username
|
|
24
|
+
userland/login/enter_webauth
|
|
25
|
+
userland/login/needs_onboarding
|
|
26
|
+
userland/login/no_authenticators
|
|
27
|
+
userland/login/remote_session_available
|
|
28
|
+
userland/onboarding/already_logged_in
|
|
29
|
+
userland/onboarding/choose_password
|
|
30
|
+
userland/onboarding/choose_webauth_nickname
|
|
31
|
+
userland/onboarding/completed
|
|
32
|
+
userland/onboarding/confirm_otp
|
|
33
|
+
userland/onboarding/confirm_password
|
|
34
|
+
userland/onboarding/confirm_webauth
|
|
35
|
+
userland/onboarding/register_otp
|
|
36
|
+
userland/onboarding/register_webauth
|
|
37
|
+
userland/onboarding/timed_out
|
|
38
|
+
userland/otp/add
|
|
39
|
+
userland/otp/confirm
|
|
40
|
+
userland/otp/register
|
|
41
|
+
userland/otp/show
|
|
42
|
+
userland/otp/successfully_changed
|
|
43
|
+
userland/otp/sudo
|
|
44
|
+
userland/password_reset/check_your_mail
|
|
45
|
+
userland/password_reset/choose_password
|
|
46
|
+
userland/password_reset/completed
|
|
47
|
+
userland/password_reset/confirm_password
|
|
48
|
+
userland/password_reset/logout_first
|
|
49
|
+
userland/password_reset/new
|
|
50
|
+
userland/password_reset/revoked
|
|
51
|
+
userland/password_reset/throttled
|
|
52
|
+
userland/password_reset/timed_out
|
|
53
|
+
userland/password_reset/wrong_user_logged_in
|
|
54
|
+
]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def covered_partials
|
|
58
|
+
Booth::Test::Support::AssertPartial.asserted_partials.to_a
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Test
|
|
3
|
+
module Support
|
|
4
|
+
class AssertLoggedIn
|
|
5
|
+
class AssertionFailedError < StandardError; end
|
|
6
|
+
|
|
7
|
+
include ::Booth::MethodObject
|
|
8
|
+
include ::Booth::Logging
|
|
9
|
+
|
|
10
|
+
option :page
|
|
11
|
+
option :scope
|
|
12
|
+
option :credential
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
tries ||= 0
|
|
16
|
+
::Capybara::Lockstep.synchronize
|
|
17
|
+
|
|
18
|
+
active_sessions.each do |session|
|
|
19
|
+
browser_session_id = ::Booth::Test::Support::GetSessionValue.call(page:, key:)
|
|
20
|
+
return true if browser_session_id == session.id.to_s
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
raise AssertionFailedError, "Expected Credential `#{credential.id}` to be logged in with a session of: #{active_sessions.map(&:id)}"
|
|
24
|
+
|
|
25
|
+
# With the Webauth pingpong it sometimes takes a little longer.
|
|
26
|
+
# And Capybara Lockstep doesn't seem to be able to detect that.
|
|
27
|
+
rescue AssertionFailedError
|
|
28
|
+
if (tries += 1) < 3
|
|
29
|
+
debug { 'Trying again...' }
|
|
30
|
+
sleep 1
|
|
31
|
+
retry
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
raise
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def active_sessions
|
|
40
|
+
credential.sessions.active_scope.sorted_scope
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def key
|
|
44
|
+
"warden.user.#{scope}.key"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Test
|
|
3
|
+
module Support
|
|
4
|
+
class AssertLoggedOut
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
option :page
|
|
8
|
+
option :scope
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
::Capybara::Lockstep.synchronize
|
|
12
|
+
|
|
13
|
+
return unless logged_in?
|
|
14
|
+
|
|
15
|
+
raise 'Expected nobody to logged in, but somebody is logged in.'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def logged_in?
|
|
21
|
+
::Booth::Test::Support::GetSessionValue.call(page:, key:)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def key
|
|
25
|
+
"warden.user.#{scope}.key"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|