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,47 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Test
|
|
3
|
+
module Userland
|
|
4
|
+
module Onboardings
|
|
5
|
+
class Timeout
|
|
6
|
+
include ::Booth::MethodObject
|
|
7
|
+
include ::Booth::Test::Helpers
|
|
8
|
+
include ActiveSupport::Testing::TimeHelpers
|
|
9
|
+
|
|
10
|
+
option :page
|
|
11
|
+
option :scope
|
|
12
|
+
option :show_onboarding_path
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
# Setup
|
|
16
|
+
freeze_time
|
|
17
|
+
|
|
18
|
+
credential = ::Booth::Models::Credential.create!(
|
|
19
|
+
username: 'alice',
|
|
20
|
+
password: 'qwrasfyxv',
|
|
21
|
+
scope:,
|
|
22
|
+
mode: :username_and_password,
|
|
23
|
+
allowed_modes: %i[username_and_password username_and_webauth]
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
onboarding = ::Booth::Models::Onboarding.create!(
|
|
27
|
+
credential_id: credential.id,
|
|
28
|
+
mode: :first_time
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Onboarding
|
|
32
|
+
|
|
33
|
+
travel 7.days - 1.minute
|
|
34
|
+
|
|
35
|
+
page.visit show_onboarding_path.sub('ID', onboarding.secret_key)
|
|
36
|
+
assert_userland_partial controller: :onboarding, step: :choose_mode
|
|
37
|
+
|
|
38
|
+
travel 2.minutes
|
|
39
|
+
|
|
40
|
+
page.visit show_onboarding_path.sub('ID', onboarding.secret_key)
|
|
41
|
+
assert_userland_partial controller: :onboarding, step: :timed_out
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Test
|
|
3
|
+
module Userland
|
|
4
|
+
module Otps
|
|
5
|
+
class Manage
|
|
6
|
+
include ::Booth::MethodObject
|
|
7
|
+
include ::Booth::Test::Helpers
|
|
8
|
+
|
|
9
|
+
option :page
|
|
10
|
+
option :scope
|
|
11
|
+
option :new_login_path
|
|
12
|
+
option :show_otp_path
|
|
13
|
+
option :after_credential, default: -> {}
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
# Setup
|
|
17
|
+
|
|
18
|
+
credential = ::Booth::Models::Credential.create!(
|
|
19
|
+
username: 'alice',
|
|
20
|
+
password: 'qwrasfyxv',
|
|
21
|
+
scope:,
|
|
22
|
+
mode: :username_and_password,
|
|
23
|
+
allowed_modes: %i[username_and_password username_password_and_otp]
|
|
24
|
+
)
|
|
25
|
+
after_credential&.call(credential.id)
|
|
26
|
+
|
|
27
|
+
# Login
|
|
28
|
+
|
|
29
|
+
page.visit new_login_path
|
|
30
|
+
|
|
31
|
+
assert_userland_partial controller: :login, step: :enter_username
|
|
32
|
+
page.fill_in :username, with: 'alice'
|
|
33
|
+
page.click_on :submit
|
|
34
|
+
|
|
35
|
+
assert_userland_partial controller: :login, step: :enter_password
|
|
36
|
+
page.fill_in :password, with: 'qwrasfyxv'
|
|
37
|
+
page.click_on :submit
|
|
38
|
+
|
|
39
|
+
# Add OTP
|
|
40
|
+
|
|
41
|
+
assert_logged_in credential: credential
|
|
42
|
+
page.visit show_otp_path
|
|
43
|
+
|
|
44
|
+
assert_userland_partial controller: :otp, step: :add
|
|
45
|
+
page.click_on :add
|
|
46
|
+
|
|
47
|
+
assert_userland_partial controller: :otp, step: :register
|
|
48
|
+
page.assert_selector '[data-booth=otpqr]'
|
|
49
|
+
page.assert_text 'otpauth://totp/'
|
|
50
|
+
page.click_on :registered
|
|
51
|
+
|
|
52
|
+
# Back one step
|
|
53
|
+
|
|
54
|
+
assert_userland_partial controller: :otp, step: :confirm
|
|
55
|
+
page.click_on :change
|
|
56
|
+
|
|
57
|
+
# Continue adding OTP
|
|
58
|
+
|
|
59
|
+
assert_userland_partial controller: :otp, step: :register
|
|
60
|
+
code = extract_otp_secret_key_and_generate_code
|
|
61
|
+
page.click_on :registered
|
|
62
|
+
|
|
63
|
+
assert_userland_partial controller: :otp, step: :confirm
|
|
64
|
+
page.fill_in :code, with: code
|
|
65
|
+
page.click_on :submit
|
|
66
|
+
|
|
67
|
+
assert_userland_partial controller: :otp, step: :successfully_changed
|
|
68
|
+
|
|
69
|
+
page.visit show_otp_path
|
|
70
|
+
assert_userland_partial controller: :otp, step: :show
|
|
71
|
+
page.assert_selector '[data-booth=otpqr]'
|
|
72
|
+
page.assert_text 'otpauth://totp/'
|
|
73
|
+
|
|
74
|
+
travel 19.minutes
|
|
75
|
+
page.visit show_otp_path
|
|
76
|
+
assert_userland_partial controller: :otp, step: :show
|
|
77
|
+
|
|
78
|
+
travel 2.minutes
|
|
79
|
+
page.visit show_otp_path
|
|
80
|
+
assert_userland_partial controller: :otp, step: :sudo
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Test
|
|
3
|
+
module Userland
|
|
4
|
+
module PasswordResets
|
|
5
|
+
class Reset
|
|
6
|
+
include ::Booth::MethodObject
|
|
7
|
+
include ::Booth::Test::Helpers
|
|
8
|
+
|
|
9
|
+
option :page
|
|
10
|
+
option :scope
|
|
11
|
+
option :new_login_path
|
|
12
|
+
option :new_password_reset_path
|
|
13
|
+
option :show_password_reset_path
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
# Setup
|
|
17
|
+
alice = ::Booth::Models::Credential.create!(
|
|
18
|
+
username: 'alice',
|
|
19
|
+
password: 'qwrasfyxv',
|
|
20
|
+
scope:,
|
|
21
|
+
mode: :username_and_password,
|
|
22
|
+
allowed_modes: %i[username_and_password]
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
bobby = ::Booth::Models::Credential.create!(
|
|
26
|
+
username: 'bobby',
|
|
27
|
+
password: 'qwrasfyxv',
|
|
28
|
+
scope:,
|
|
29
|
+
mode: :username_and_password,
|
|
30
|
+
allowed_modes: %i[username_and_password]
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
bobbys_password_reset = ::Booth::Models::PasswordReset.create!(
|
|
34
|
+
credential: bobby,
|
|
35
|
+
creator_ip: '198.51.100.50'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
bobbys_other_password_reset = ::Booth::Models::PasswordReset.create!(
|
|
39
|
+
credential: bobby,
|
|
40
|
+
creator_ip: '198.51.100.51'
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Request Password Reset
|
|
44
|
+
page.visit new_login_path
|
|
45
|
+
assert_userland_partial controller: :login, step: :enter_username
|
|
46
|
+
page.fill_in :username, with: 'alice'
|
|
47
|
+
page.click_on :submit
|
|
48
|
+
assert_userland_partial controller: :login, step: :enter_password
|
|
49
|
+
page.click_on :forgot
|
|
50
|
+
assert_userland_partial controller: :password_reset, step: :new
|
|
51
|
+
page.fill_in :email, with: 'alice@example.com'
|
|
52
|
+
page.click_on :submit
|
|
53
|
+
assert_userland_partial controller: :password_reset, step: :check_your_mail
|
|
54
|
+
|
|
55
|
+
# Forgot Login password
|
|
56
|
+
page.visit new_login_path
|
|
57
|
+
assert_userland_partial controller: :login, step: :enter_password
|
|
58
|
+
page.fill_in :password, with: 'qwrasfyxv'
|
|
59
|
+
page.click_on :submit
|
|
60
|
+
assert_logged_in credential: alice
|
|
61
|
+
page.visit new_password_reset_path
|
|
62
|
+
assert_userland_partial controller: :password_reset, step: :logout_first
|
|
63
|
+
|
|
64
|
+
# Reset password as wrong user
|
|
65
|
+
page.visit show_password_reset_path.sub('ID', bobbys_password_reset.secret_key)
|
|
66
|
+
assert_userland_partial controller: :password_reset, step: :wrong_user_logged_in
|
|
67
|
+
page.click_on :logout
|
|
68
|
+
assert_logged_out
|
|
69
|
+
|
|
70
|
+
# Timed out
|
|
71
|
+
travel 2.hours + 1.second
|
|
72
|
+
page.visit show_password_reset_path.sub('ID', bobbys_password_reset.secret_key)
|
|
73
|
+
assert_userland_partial controller: :password_reset, step: :timed_out
|
|
74
|
+
travel_back
|
|
75
|
+
|
|
76
|
+
# Reset password
|
|
77
|
+
page.visit show_password_reset_path.sub('ID', bobbys_password_reset.secret_key)
|
|
78
|
+
assert_userland_partial controller: :password_reset, step: :choose_password
|
|
79
|
+
page.fill_in :password, with: 'wetsdgxcb'
|
|
80
|
+
page.click_on :submit
|
|
81
|
+
page.click_on :change
|
|
82
|
+
assert_userland_partial controller: :password_reset, step: :choose_password
|
|
83
|
+
page.fill_in :password, with: 'rtufgjvbm'
|
|
84
|
+
page.click_on :submit
|
|
85
|
+
assert_userland_partial controller: :password_reset, step: :confirm_password
|
|
86
|
+
page.fill_in :password, with: 'rtufgjvbm'
|
|
87
|
+
page.click_on :submit
|
|
88
|
+
assert_userland_partial controller: :password_reset, step: :completed
|
|
89
|
+
|
|
90
|
+
# Refresh page
|
|
91
|
+
page.visit show_password_reset_path.sub('ID', bobbys_password_reset.secret_key)
|
|
92
|
+
assert_userland_partial controller: :password_reset, step: :completed
|
|
93
|
+
|
|
94
|
+
# Try a revoked token
|
|
95
|
+
page.visit show_password_reset_path.sub('ID', bobbys_other_password_reset.secret_key)
|
|
96
|
+
assert_userland_partial controller: :password_reset, step: :revoked
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Test
|
|
3
|
+
module Userland
|
|
4
|
+
class Scenario
|
|
5
|
+
attr_accessor :page, :scope, :new_login_path, :show_onboarding_path, :show_otp_path, :new_password_reset_path,
|
|
6
|
+
:show_password_reset_path, :after_credential
|
|
7
|
+
|
|
8
|
+
def initialize(klass)
|
|
9
|
+
@klass = klass
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def name
|
|
13
|
+
@klass.to_s.underscore.parameterize(separator: '_')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
arguments = @klass.dry_initializer.public_attributes(self)
|
|
18
|
+
@klass.call(**arguments)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.scenarios(skip_password_resets: false) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
23
|
+
yield Scenario.new(Booth::Test::Userland::Logins::MissingAuthenticators)
|
|
24
|
+
yield Scenario.new(Booth::Test::Userland::Logins::MissingOnboarding)
|
|
25
|
+
yield Scenario.new(Booth::Test::Userland::Logins::UsernameAndPassword)
|
|
26
|
+
yield Scenario.new(Booth::Test::Userland::Logins::UsernameAndWebauth)
|
|
27
|
+
yield Scenario.new(Booth::Test::Userland::Logins::UsernamePasswordAndOtp)
|
|
28
|
+
yield Scenario.new(Booth::Test::Userland::Logins::UsernamePasswordAndWebauth)
|
|
29
|
+
yield Scenario.new(Booth::Test::Userland::Onboardings::AlreadyLoggedIn)
|
|
30
|
+
yield Scenario.new(Booth::Test::Userland::Onboardings::Otp)
|
|
31
|
+
yield Scenario.new(Booth::Test::Userland::Onboardings::Password)
|
|
32
|
+
yield Scenario.new(Booth::Test::Userland::Onboardings::Timeout)
|
|
33
|
+
yield Scenario.new(Booth::Test::Userland::Otps::Manage)
|
|
34
|
+
yield Scenario.new(Booth::Test::Userland::PasswordResets::Reset) unless skip_password_resets
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Test
|
|
3
|
+
module Webauthn
|
|
4
|
+
class Disable
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
include ::Booth::MethodObject
|
|
7
|
+
|
|
8
|
+
option :devtools
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
debug { 'Disabling Chrome Virtual Authenticator Environment...' }
|
|
12
|
+
devtools.send_cmd 'WebAuthn.disable'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Test
|
|
3
|
+
module Webauthn
|
|
4
|
+
class Enable
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
include ::Booth::MethodObject
|
|
7
|
+
|
|
8
|
+
option :devtools
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
WebAuthn.configuration.origin = Capybara.current_session.server.base_url
|
|
12
|
+
|
|
13
|
+
debug { 'Ensuring enabled Chrome Virtual Authenticator Environment...' }
|
|
14
|
+
devtools.send_cmd 'WebAuthn.enable'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Test
|
|
3
|
+
module Webauthn
|
|
4
|
+
module VirtualAuthenticators
|
|
5
|
+
class Create
|
|
6
|
+
include ::Booth::Logging
|
|
7
|
+
include ::Booth::MethodObject
|
|
8
|
+
|
|
9
|
+
option :page
|
|
10
|
+
option :has_user_verification
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
options = ::Selenium::WebDriver::VirtualAuthenticatorOptions.new
|
|
14
|
+
options.user_verification = has_user_verification
|
|
15
|
+
options.user_verified = true
|
|
16
|
+
|
|
17
|
+
debug { "Registering Virtual Authenticator... #{options.as_json}" }
|
|
18
|
+
page.driver.browser.add_virtual_authenticator(options)
|
|
19
|
+
|
|
20
|
+
# debug { "Created #{authenticator.id}" }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# See https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/#type-VirtualAuthenticatorOptions
|
|
24
|
+
# def options
|
|
25
|
+
# {
|
|
26
|
+
# protocol: :ctap2,
|
|
27
|
+
# transport: :internal,
|
|
28
|
+
# hasResidentKey: false, # Chrome should not have to reveal a list of existing virtual authenticator IDs.
|
|
29
|
+
# # isUserConsenting: true, # Not sure, this option exists in selenium but not in chrome?
|
|
30
|
+
# hasUserVerification: has_user_verification,
|
|
31
|
+
# isUserVerified: true,
|
|
32
|
+
# }
|
|
33
|
+
# end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Test
|
|
3
|
+
module Webauthn
|
|
4
|
+
module VirtualAuthenticators
|
|
5
|
+
class Destroy
|
|
6
|
+
include ::Booth::Logging
|
|
7
|
+
include ::Booth::MethodObject
|
|
8
|
+
|
|
9
|
+
option :devtools
|
|
10
|
+
option :id
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
debug { "Removing Virtual Authenticator with ID #{id}" }
|
|
14
|
+
devtools.send_cmd 'WebAuthn.removeVirtualAuthenticator', authenticatorId: id
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/booth/test.rb
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Just in case
|
|
2
|
+
raise 'Requiring `booth/test` is only intended for testing your code' unless Rails.env.test?
|
|
3
|
+
|
|
4
|
+
def try_to_load_gem(gem_name, require_name = nil)
|
|
5
|
+
require_name = gem_name if require_name.nil?
|
|
6
|
+
require require_name
|
|
7
|
+
rescue LoadError => e
|
|
8
|
+
raise "Please add the `#{gem_name}` gem to your test group (NOT development or production) - #{e.message}"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Gems
|
|
12
|
+
try_to_load_gem 'capybara-lockstep'
|
|
13
|
+
try_to_load_gem 'rack_session_access'
|
|
14
|
+
try_to_load_gem 'selenium-devtools', 'selenium/devtools'
|
|
15
|
+
try_to_load_gem 'selenium-webdriver'
|
|
16
|
+
require 'rack_session_access/capybara'
|
|
17
|
+
|
|
18
|
+
# Internal Helpers
|
|
19
|
+
require_relative 'test/helpers'
|
|
20
|
+
require_relative 'test/support/force_login'
|
|
21
|
+
|
|
22
|
+
# Tests
|
|
23
|
+
require_relative 'test/userland/logins/missing_authenticators'
|
|
24
|
+
require_relative 'test/userland/logins/missing_onboarding'
|
|
25
|
+
require_relative 'test/userland/logins/username_and_password'
|
|
26
|
+
require_relative 'test/userland/logins/username_and_webauth'
|
|
27
|
+
require_relative 'test/userland/logins/username_password_and_otp'
|
|
28
|
+
require_relative 'test/userland/logins/username_password_and_webauth'
|
|
29
|
+
require_relative 'test/userland/onboardings/already_logged_in'
|
|
30
|
+
require_relative 'test/userland/onboardings/otp'
|
|
31
|
+
require_relative 'test/userland/onboardings/password'
|
|
32
|
+
require_relative 'test/userland/onboardings/timeout'
|
|
33
|
+
require_relative 'test/userland/otps/manage'
|
|
34
|
+
require_relative 'test/userland/password_resets/reset'
|
|
35
|
+
|
|
36
|
+
# Public API
|
|
37
|
+
require_relative 'test/userland'
|
|
38
|
+
|
|
39
|
+
module Booth
|
|
40
|
+
module Test
|
|
41
|
+
def self.middleware
|
|
42
|
+
::RackSessionAccess::Middleware
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.force_login(...)
|
|
46
|
+
::Booth::Test::Support::ForceLogin.call(...)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
Capybara.configure do |config|
|
|
52
|
+
config.test_id = 'data-booth' # How Booth interacts with your HTML elements.
|
|
53
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
class ExtractFlashMessages
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
option :from
|
|
8
|
+
option :to
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
check_arguments!
|
|
12
|
+
return unless from.respond_to?(:public_message)
|
|
13
|
+
return if from.public_message.blank?
|
|
14
|
+
|
|
15
|
+
if from.success?
|
|
16
|
+
debug { "Saving flash notice: #{from.public_message}" }
|
|
17
|
+
to[:notice] = from.public_message
|
|
18
|
+
else
|
|
19
|
+
debug { "Saving flash alert: #{from.public_message}" }
|
|
20
|
+
to[:alert] = from.public_message
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def check_arguments!
|
|
27
|
+
raise(ArgumentError, 'You can only extract flash messages from something that is not nil') if from.nil?
|
|
28
|
+
|
|
29
|
+
return if to.respond_to?(:notice=)
|
|
30
|
+
|
|
31
|
+
raise ArgumentError, "Please pass in `to: flash` for public flash messages not #{to.inspect}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
module Logins
|
|
4
|
+
class Create
|
|
5
|
+
include ::Booth::Concerns::Action
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
request.must_be_post!
|
|
9
|
+
|
|
10
|
+
do_transition
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def transitions
|
|
16
|
+
[
|
|
17
|
+
::Booth::Logins::Transitions::Create::ChooseUsername,
|
|
18
|
+
::Booth::Logins::Transitions::Create::EnterOtp,
|
|
19
|
+
::Booth::Logins::Transitions::Create::SkipRemotes,
|
|
20
|
+
::Booth::Logins::Transitions::Create::VerifyPassword,
|
|
21
|
+
::Booth::Logins::Transitions::Create::WebauthAuthenticationInitiation,
|
|
22
|
+
::Booth::Logins::Transitions::Create::WebauthAuthenticationVerification,
|
|
23
|
+
]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
module Logins
|
|
4
|
+
class Destroy
|
|
5
|
+
include ::Booth::Concerns::Action
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
request.must_be_delete!
|
|
9
|
+
request.must_be_html!
|
|
10
|
+
|
|
11
|
+
if request.authentication.logged_in?
|
|
12
|
+
public_message = I18n.t('booth.successfully_logged_out')
|
|
13
|
+
::Booth::Audits::Register::Logout.call credential:,
|
|
14
|
+
ip: request.ip,
|
|
15
|
+
agent: request.agent
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
reset!
|
|
19
|
+
|
|
20
|
+
Tron.success :logged_out, return_path: request.return_path,
|
|
21
|
+
public_message:
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def credential
|
|
27
|
+
@credential ||= ::Booth::Models::Credential.find(request.authentication.credential_id)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def reset!
|
|
31
|
+
request.storage.login.reset
|
|
32
|
+
request.authentication.logout
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
module Logins
|
|
4
|
+
class New
|
|
5
|
+
include ::Booth::Concerns::Action
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
request.must_be_get!
|
|
9
|
+
request.must_be_html!
|
|
10
|
+
|
|
11
|
+
if already_logged_in?
|
|
12
|
+
# Bail out early if it would conflict with another session.
|
|
13
|
+
# Because this could lead to strong confusion for the end-user.
|
|
14
|
+
::Booth::Userland::Logins::Transitions::New::AlreadyLoggedIn.call(request:)
|
|
15
|
+
|
|
16
|
+
elsif storage.timed_out?
|
|
17
|
+
# The browser cookie just destroyed itself.
|
|
18
|
+
::Booth::Userland::Logins::Transitions::New::TimedOut.call(request:)
|
|
19
|
+
|
|
20
|
+
elsif storage.credential_for_username.blank?
|
|
21
|
+
# First of all, we need a username.
|
|
22
|
+
# Without it we wouldn't know which credential to look for.
|
|
23
|
+
::Booth::Userland::Logins::Transitions::New::NoUsernameChosen.call(request:)
|
|
24
|
+
|
|
25
|
+
elsif storage.credential_for_username.mode_first_time?
|
|
26
|
+
# If that username is not initialized, bail out again.
|
|
27
|
+
# This should be an informative site with remedy information.
|
|
28
|
+
::Booth::Userland::Logins::Transitions::New::ModeFirstTime.call(request:)
|
|
29
|
+
|
|
30
|
+
elsif remote_session_available?
|
|
31
|
+
# If the user is logged in on another device,
|
|
32
|
+
# suggest that that device is used as a remote to login here.
|
|
33
|
+
::Booth::Userland::Logins::Transitions::New::RemoteSessionAvailable.call(request:)
|
|
34
|
+
|
|
35
|
+
elsif storage.credential_for_username.mode_username_and_password?
|
|
36
|
+
::Booth::Userland::Logins::Transitions::New::ModeUsernameAndPassword.call(request:)
|
|
37
|
+
|
|
38
|
+
elsif storage.credential_for_username.mode_username_password_and_otp?
|
|
39
|
+
::Booth::Userland::Logins::Transitions::New::ModeUsernamePasswordAndOtp.call(request:)
|
|
40
|
+
|
|
41
|
+
elsif storage.credential_for_username.mode_username_password_and_webauth?
|
|
42
|
+
::Booth::Userland::Logins::Transitions::New::ModeUsernamePasswordAndWebauth.call(request:)
|
|
43
|
+
|
|
44
|
+
elsif storage.credential_for_username.mode_username_and_webauth?
|
|
45
|
+
::Booth::Userland::Logins::Transitions::New::ModeUsernameAndWebauth.call(request:)
|
|
46
|
+
|
|
47
|
+
else
|
|
48
|
+
raise 'Invalid Login State'
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def already_logged_in?
|
|
55
|
+
request.authentication.logged_in?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def remote_session_available?
|
|
59
|
+
return false if storage.skip_remotes?
|
|
60
|
+
|
|
61
|
+
storage.credential_for_username.remote_session_available?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def storage
|
|
65
|
+
request.storage.login
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Logins
|
|
3
|
+
module Transitions
|
|
4
|
+
module Create
|
|
5
|
+
class ChooseUsername
|
|
6
|
+
include ::Booth::Concerns::Transition
|
|
7
|
+
|
|
8
|
+
def self.applicable?(params:)
|
|
9
|
+
params.dig :login, :username
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
finding = ::Booth::Credentials::FindByUsername.call(username: username_param)
|
|
14
|
+
|
|
15
|
+
finding.on_success do
|
|
16
|
+
# Each time a username was entered, we destroy and recreate the Contest for this Credential.
|
|
17
|
+
contest = ::Booth::Contests::SetForLogin.call(request:, credential_id: finding.credential.id).contest
|
|
18
|
+
storage.contest_for_username = contest
|
|
19
|
+
storage.credential_for_username = finding.credential
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Whether the username is valid or not, we persist it in the cookie,
|
|
23
|
+
# so that it can be shown again in the username text input.
|
|
24
|
+
storage.username = finding.normalized_invalid_username
|
|
25
|
+
finding
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def username_param
|
|
31
|
+
params.require(:login).permit(:username)[:username]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def storage
|
|
35
|
+
request.storage.login
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|