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,70 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Logins
|
|
3
|
+
module Transitions
|
|
4
|
+
module Create
|
|
5
|
+
class EnterOtp
|
|
6
|
+
include ::Booth::Concerns::Transition
|
|
7
|
+
|
|
8
|
+
def self.applicable?(params:)
|
|
9
|
+
params.dig :login, :otp
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
do_check_stale_session
|
|
14
|
+
.on_success { do_find_credential }
|
|
15
|
+
.on_success { do_check_otp }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# Helpers
|
|
21
|
+
|
|
22
|
+
delegate :sudo, to: :request
|
|
23
|
+
|
|
24
|
+
def do_check_stale_session
|
|
25
|
+
return Tron.success :session_not_stale unless storage.timed_out?
|
|
26
|
+
|
|
27
|
+
public_message = I18n.t('booth.login_timeout', lifespan_minutes: (storage.lifespan / 60))
|
|
28
|
+
Tron.failure :stale_session, step: :enter_username,
|
|
29
|
+
public_message:
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def do_find_credential
|
|
33
|
+
@credential = storage.password_authenticated_credential
|
|
34
|
+
return Tron.success :found_credential if @credential
|
|
35
|
+
|
|
36
|
+
Tron.failure :missing_credential
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def do_check_credential_mode
|
|
40
|
+
return Tron.success :credential_is_otp if @credential.mode_username_password_and_otp?
|
|
41
|
+
|
|
42
|
+
Tron.failure :credential_is_not_otp
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def do_check_otp
|
|
46
|
+
checking = ::Booth::Credentials::OtpAuthentication.call(
|
|
47
|
+
credential: @credential,
|
|
48
|
+
code: code_param,
|
|
49
|
+
ip: request.ip,
|
|
50
|
+
agent: request.agent
|
|
51
|
+
)
|
|
52
|
+
return checking if checking.failure
|
|
53
|
+
|
|
54
|
+
storage.reset
|
|
55
|
+
::Booth::Sessions::CreateAndLogin.call(credential: @credential, request:)
|
|
56
|
+
Tron.success :login_completed
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def code_param
|
|
60
|
+
params.require(:login).permit(:otp)[:otp]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def storage
|
|
64
|
+
request.storage.login
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Logins
|
|
3
|
+
module Transitions
|
|
4
|
+
module Create
|
|
5
|
+
class SkipRemotes
|
|
6
|
+
include ::Booth::Concerns::Transition
|
|
7
|
+
|
|
8
|
+
def self.applicable?(params:)
|
|
9
|
+
params[:skip_remotes].present?
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
storage.skip_remotes
|
|
14
|
+
Tron.success :skipping_remotes
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def storage
|
|
18
|
+
request.storage.login
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Logins
|
|
3
|
+
module Transitions
|
|
4
|
+
module Create
|
|
5
|
+
class VerifyPassword
|
|
6
|
+
include ::Booth::Concerns::Transition
|
|
7
|
+
|
|
8
|
+
def self.applicable?(params:)
|
|
9
|
+
params.dig :login, :password
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
do_check_stale_session
|
|
14
|
+
.on_success { do_find_credential }
|
|
15
|
+
.on_success { do_check_password }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Helpers
|
|
19
|
+
|
|
20
|
+
def do_check_stale_session
|
|
21
|
+
return Tron.success :session_not_stale unless storage.timed_out?
|
|
22
|
+
|
|
23
|
+
public_message = I18n.t('booth.login_timeout', lifespan_minutes: (storage.lifespan / 60))
|
|
24
|
+
Tron.failure :stale_session, step: :enter_username,
|
|
25
|
+
public_message:
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def do_find_credential
|
|
29
|
+
@credential = storage.credential_for_username
|
|
30
|
+
return Tron.success :found_credential if @credential
|
|
31
|
+
|
|
32
|
+
Tron.failure :missing_credential
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def do_check_password
|
|
36
|
+
checking = ::Booth::Credentials::PasswordAuthentication.call(
|
|
37
|
+
credential: @credential,
|
|
38
|
+
password: password_param,
|
|
39
|
+
ip: request.ip,
|
|
40
|
+
agent: request.agent
|
|
41
|
+
)
|
|
42
|
+
return checking if checking.failure
|
|
43
|
+
|
|
44
|
+
sudo.password!
|
|
45
|
+
|
|
46
|
+
# TODO: Re-check with haveibeenpwned and flag `flagged_pwned_at` password as insecure.
|
|
47
|
+
|
|
48
|
+
if @credential.mode_username_and_password?
|
|
49
|
+
::Booth::Sessions::CreateAndLogin.call(credential: @credential, request:)
|
|
50
|
+
return Tron.success :login_completed, return_path: request.return_path
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
storage.password_authenticated_credential = @credential
|
|
54
|
+
Tron.success :correct_password
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def password_param
|
|
58
|
+
params.require(:login).permit(:password)[:password]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def storage
|
|
62
|
+
request.storage.login
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
delegate :sudo, to: :request
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Logins
|
|
3
|
+
module Transitions
|
|
4
|
+
module Create
|
|
5
|
+
class WebauthAuthenticationInitiation
|
|
6
|
+
include ::Booth::Concerns::Transition
|
|
7
|
+
|
|
8
|
+
def self.applicable?(params:)
|
|
9
|
+
params[:webauth] && !params[:type]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
do_check_stale_session
|
|
14
|
+
.on_success { do_find_credential }
|
|
15
|
+
.on_success { do_check_webauth }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Helpers
|
|
19
|
+
|
|
20
|
+
def do_check_stale_session
|
|
21
|
+
return Tron.success :session_not_stale unless storage.timed_out?
|
|
22
|
+
|
|
23
|
+
public_message = I18n.t('booth.login_timeout', lifespan_minutes: (storage.lifespan / 60))
|
|
24
|
+
Tron.failure :stale_session, step: :enter_username,
|
|
25
|
+
public_message:
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def do_find_credential
|
|
29
|
+
@credential = storage.credential_for_username
|
|
30
|
+
return Tron.success :found_credential if @credential
|
|
31
|
+
|
|
32
|
+
debug { 'I do not know the credential for this username' }
|
|
33
|
+
Tron.failure :missing_credential
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def do_check_webauth
|
|
37
|
+
debug { 'Preparing webauth challenge...' }
|
|
38
|
+
challenging = Booth::Credentials::WebauthChallenge.call(credential: @credential)
|
|
39
|
+
result = Tron.success :webauth_for_you, public_json: challenging.options_for_get, http_status: :ok
|
|
40
|
+
debug { "The challenge is #{challenging.challenge}" }
|
|
41
|
+
storage.webauthn_challenge = challenging.challenge
|
|
42
|
+
debug { "Responding with JSON: #{result.public_json}" }
|
|
43
|
+
result
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def storage
|
|
47
|
+
request.storage.login
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
delegate :authentication, to: :request
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Logins
|
|
3
|
+
module Transitions
|
|
4
|
+
module Create
|
|
5
|
+
class WebauthAuthenticationVerification
|
|
6
|
+
include ::Booth::Concerns::Transition
|
|
7
|
+
|
|
8
|
+
def self.applicable?(params:)
|
|
9
|
+
params[:webauth] && params[:type]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
do_check_stale_session
|
|
14
|
+
.on_success { do_find_credential }
|
|
15
|
+
.on_success { do_find_challenge }
|
|
16
|
+
.on_success { do_check_webauth }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
delegate :sudo, to: :request
|
|
22
|
+
|
|
23
|
+
# Helpers
|
|
24
|
+
|
|
25
|
+
def do_check_stale_session
|
|
26
|
+
return Tron.success :session_not_stale unless storage.timed_out?
|
|
27
|
+
|
|
28
|
+
public_message = I18n.t('booth.login_timeout', lifespan_minutes: (storage.lifespan / 60))
|
|
29
|
+
debug { public_message }
|
|
30
|
+
Tron.failure :stale_session, step: :enter_username,
|
|
31
|
+
public_message:
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def do_find_credential
|
|
35
|
+
@credential = storage.password_authenticated_credential
|
|
36
|
+
return Tron.success :found_credential if @credential
|
|
37
|
+
|
|
38
|
+
@credential = storage.credential_for_username
|
|
39
|
+
return Tron.success :found_credential if @credential
|
|
40
|
+
|
|
41
|
+
debug { 'I do not know the credential for this username' }
|
|
42
|
+
Tron.failure :missing_credential
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def do_find_challenge
|
|
46
|
+
return Tron.success :challenge_ongoing if storage.webauthn_challenge.present?
|
|
47
|
+
|
|
48
|
+
debug { 'There is no corresponding challenge in the session' }
|
|
49
|
+
Tron.failure :no_session_challenge, public_json: {}, http_status: :unprocessable_entity
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def do_check_webauth
|
|
53
|
+
verification = ::Booth::Webauth::AuthenticationVerification.call(
|
|
54
|
+
request:,
|
|
55
|
+
credential_id: @credential.id,
|
|
56
|
+
challenge: storage.webauthn_challenge
|
|
57
|
+
)
|
|
58
|
+
return verification if verification.failure?
|
|
59
|
+
|
|
60
|
+
# sudo.webauth! # Why was this up here as well? I forgot.
|
|
61
|
+
if @credential.mode_username_and_webauth?
|
|
62
|
+
::Booth::Sessions::CreateAndLogin.call(credential: verification.credential, request:)
|
|
63
|
+
elsif @credential.mode_username_password_and_webauth? && @credential == storage.password_authenticated_credential
|
|
64
|
+
::Booth::Sessions::CreateAndLogin.call(credential: verification.credential, request:)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
sudo.webauth! # This also re-sudos after reset caused by potential login above.
|
|
68
|
+
|
|
69
|
+
Tron.success :login_completed, public_json: {},
|
|
70
|
+
http_status: :created
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def storage
|
|
74
|
+
request.storage.login
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
module Logins
|
|
4
|
+
module Transitions
|
|
5
|
+
module New
|
|
6
|
+
class AlreadyLoggedIn
|
|
7
|
+
include ::Booth::Concerns::Transition
|
|
8
|
+
include ::Booth::Userland::Logins::Transitions::New::Fallible
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
debug { "Good, it looks like you've managed to login" }
|
|
12
|
+
debug { "Sending you to #{request.return_path.inspect}" } if request.return_path
|
|
13
|
+
|
|
14
|
+
Tron.success :cannot_login_because_already_logged_in, return_path: request.return_path
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
module Logins
|
|
4
|
+
module Transitions
|
|
5
|
+
module New
|
|
6
|
+
module Fallible
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def fail_with(code, attributes)
|
|
12
|
+
attributes.merge! username: storage.username,
|
|
13
|
+
login_lifespan: storage.lifespan,
|
|
14
|
+
seconds_until_auto_reset: storage.seconds_until_auto_reset
|
|
15
|
+
|
|
16
|
+
Tron.failure code, attributes
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def storage
|
|
20
|
+
request.storage.login
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
module Logins
|
|
4
|
+
module Transitions
|
|
5
|
+
module New
|
|
6
|
+
class ModeFirstTime
|
|
7
|
+
include ::Booth::Concerns::Transition
|
|
8
|
+
include ::Booth::Userland::Logins::Transitions::New::Fallible
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
debug { 'This credential has not gone through onboarding yet.' }
|
|
12
|
+
# storage.login.reset_credential_for_username
|
|
13
|
+
fail_with :need_webauth, step: :needs_onboarding
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
module Logins
|
|
4
|
+
module Transitions
|
|
5
|
+
module New
|
|
6
|
+
class ModeUsernameAndPassword
|
|
7
|
+
include ::Booth::Concerns::Transition
|
|
8
|
+
include ::Booth::Userland::Logins::Transitions::New::Fallible
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
debug { 'I have your username and I only need your password' }
|
|
12
|
+
|
|
13
|
+
fail_with :need_password, step: :enter_password
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
module Logins
|
|
4
|
+
module Transitions
|
|
5
|
+
module New
|
|
6
|
+
class ModeUsernameAndWebauth
|
|
7
|
+
include ::Booth::Concerns::Transition
|
|
8
|
+
include ::Booth::Userland::Logins::Transitions::New::Fallible
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
debug { 'I have your username and I only need your Webauth' }
|
|
12
|
+
|
|
13
|
+
if storage.credential_for_username.registered_authenticator_ids.any?
|
|
14
|
+
debug { 'I know that the user has an authenticator that is to be challenged' }
|
|
15
|
+
return fail_with(:need_webauth, step: :enter_webauth)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
debug { "Apparently we don't know any authenticators of this user" }
|
|
19
|
+
fail_with :no_authenticators, step: :no_authenticators
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
module Logins
|
|
4
|
+
module Transitions
|
|
5
|
+
module New
|
|
6
|
+
class ModeUsernamePasswordAndOtp
|
|
7
|
+
include ::Booth::Concerns::Transition
|
|
8
|
+
include ::Booth::Userland::Logins::Transitions::New::Fallible
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
if storage.password_authenticated_credential
|
|
12
|
+
debug { 'I have your username and your password, now I need your OTP' }
|
|
13
|
+
fail_with :need_otp, step: :enter_otp
|
|
14
|
+
else
|
|
15
|
+
debug { 'I have your username but not your password, let alone your OTP' }
|
|
16
|
+
fail_with :need_password_and_otp, step: :enter_password
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
module Logins
|
|
4
|
+
module Transitions
|
|
5
|
+
module New
|
|
6
|
+
class ModeUsernamePasswordAndWebauth
|
|
7
|
+
include ::Booth::Concerns::Transition
|
|
8
|
+
include ::Booth::Userland::Logins::Transitions::New::Fallible
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
if storage.password_authenticated_credential
|
|
12
|
+
debug { 'I have your username and your password, now I need your Webauth' }
|
|
13
|
+
fail_with :need_webauth, step: :enter_webauth
|
|
14
|
+
else
|
|
15
|
+
debug { 'I have your username but not your password, let alone your Webauth' }
|
|
16
|
+
fail_with :need_password_and_webauth, step: :enter_password
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
module Logins
|
|
4
|
+
module Transitions
|
|
5
|
+
module New
|
|
6
|
+
class NoUsernameChosen
|
|
7
|
+
include ::Booth::Concerns::Transition
|
|
8
|
+
include ::Booth::Userland::Logins::Transitions::New::Fallible
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
debug { "I don't know of any valid username" }
|
|
12
|
+
fail_with :no_username_chosen, step: :enter_username
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
module Logins
|
|
4
|
+
module Transitions
|
|
5
|
+
module New
|
|
6
|
+
class RemoteSessionAvailable
|
|
7
|
+
include ::Booth::Concerns::Transition
|
|
8
|
+
include ::Booth::Userland::Logins::Transitions::New::Fallible
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
# Prerequisites that were arranged by previous transitions.
|
|
12
|
+
raise(::Booth::Errors::Internal, 'Expected a Credential in the cookie.') unless credential
|
|
13
|
+
|
|
14
|
+
do_respond
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def do_respond
|
|
20
|
+
if contest&.recently_responded?
|
|
21
|
+
debug { 'Your Contest was solved on the other device. Logging you in...' }
|
|
22
|
+
# Yes, it's unusual for a GET request to log you in.
|
|
23
|
+
# We are polling this page and when the Contest was solved remotely, we log this user in.
|
|
24
|
+
::Booth::Sessions::CreateAndLogin.call(credential:, request:)
|
|
25
|
+
|
|
26
|
+
elsif contest&.recently_created?
|
|
27
|
+
debug { 'If you like, you can try a remote device to log you in here.' }
|
|
28
|
+
# Having entered a username earlier, we can be sure to find a corresponding Contest.
|
|
29
|
+
expected_code = contest.formatted_code
|
|
30
|
+
|
|
31
|
+
fail_with :contest_available,
|
|
32
|
+
step: :remote_session_available,
|
|
33
|
+
expected_code:
|
|
34
|
+
else
|
|
35
|
+
fail_with :invalid_contest,
|
|
36
|
+
step: :remote_session_expired
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def credential
|
|
41
|
+
request.storage.login.credential_for_username
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def contest
|
|
45
|
+
request.storage.login.contest_for_username
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
module Logins
|
|
4
|
+
module Transitions
|
|
5
|
+
module New
|
|
6
|
+
class TimedOut
|
|
7
|
+
include ::Booth::Concerns::Transition
|
|
8
|
+
include ::Booth::Userland::Logins::Transitions::New::Fallible
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
debug { 'Stale session detected, you need to start all over again.' }
|
|
12
|
+
|
|
13
|
+
fail_with :no_username_chosen, step: :enter_username,
|
|
14
|
+
public_message:
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def public_message
|
|
18
|
+
I18n.t 'booth.login_timeout', lifespan_minutes: (storage.lifespan / 60)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Userland
|
|
3
|
+
module Onboardings
|
|
4
|
+
class Show
|
|
5
|
+
include ::Booth::Concerns::Action
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
request.must_be_get!
|
|
9
|
+
request.must_be_html!
|
|
10
|
+
|
|
11
|
+
do_find_onboarding
|
|
12
|
+
.on_success { do_check_logged_out }
|
|
13
|
+
.on_success { do_access_onboarding }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def do_find_onboarding
|
|
17
|
+
finding = ::Booth::Onboardings::Find.call(secret_key:)
|
|
18
|
+
finding.on_failure do
|
|
19
|
+
return Tron.failure finding.failure, step: :not_found, public_message: finding.public_message
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
@onboarding = finding.onboarding
|
|
23
|
+
finding
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def do_check_logged_out
|
|
27
|
+
unless request.authentication.logged_in?
|
|
28
|
+
debug { "Good, nobody happens to be already logged in in scope #{@onboarding.scope}" }
|
|
29
|
+
return Tron.success :not_logged_in
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if request.authentication.logged_in_as?(credential: @onboarding.credential)
|
|
33
|
+
debug { "#{@onboarding.credential.username} is already logged in in scope #{@onboarding.scope}" }
|
|
34
|
+
return Tron.success :logged_in_as_same_credential
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
debug do
|
|
38
|
+
"Logged in as user #{request.authentication.username.inspect} but trying to onboard as #{@onboarding.username.inspect}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
Tron.failure :already_logged_in, step: :already_logged_in,
|
|
42
|
+
secret_key: @onboarding.secret_key,
|
|
43
|
+
username: @onboarding.username
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# TODO: Check recently created?
|
|
47
|
+
|
|
48
|
+
def do_access_onboarding
|
|
49
|
+
@onboarding.update! accessed_at: Time.current if @onboarding.accessed_at.blank?
|
|
50
|
+
|
|
51
|
+
debug do
|
|
52
|
+
"Accessed Onboarding #{@onboarding.id.inspect} (Step #{@onboarding.step}) and Credential #{@onboarding.credential_id.inspect}"
|
|
53
|
+
end
|
|
54
|
+
Tron.success :onboarding_accessed, credential_id: @onboarding.credential_id,
|
|
55
|
+
step: @onboarding.step,
|
|
56
|
+
username: @onboarding.username,
|
|
57
|
+
mode: ::Booth::Mode.find(@onboarding.mode),
|
|
58
|
+
secret_key: @onboarding.secret_key,
|
|
59
|
+
allowed_modes: ::Booth::Mode.wrap(@onboarding.allowed_modes),
|
|
60
|
+
authenticator_id: @onboarding.authenticator_id.presence,
|
|
61
|
+
authenticator_nickname: @onboarding.authenticator_nickname.presence,
|
|
62
|
+
otp_provisioning_svg: @onboarding.otp_provisioning_svg,
|
|
63
|
+
otp_provisioning_url: @onboarding.otp_provisioning_url,
|
|
64
|
+
minlength: @onboarding.class.password_minlength,
|
|
65
|
+
passwordrules: @onboarding.class.passwordrules
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def secret_key
|
|
69
|
+
params[:id]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|