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,28 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
class Create
|
|
4
|
+
include ::Booth::MethodObject
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
option :username
|
|
8
|
+
option :allowed_modes
|
|
9
|
+
option :scope, default: -> { :default }
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
if credential.save
|
|
13
|
+
debug { "Successfully persisted a new Booth::Models::Credential record with ID #{credential.id.inspect}" }
|
|
14
|
+
Tron.success :credential_created, credential:
|
|
15
|
+
else
|
|
16
|
+
debug { 'Could not persist a new Booth::Models::Credential record' }
|
|
17
|
+
Tron.failure :persistence_failed, error: credential.errors.full_messages.to_sentence
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def credential
|
|
24
|
+
@credential ||= ::Booth::Models::Credential.new(username:, allowed_modes:, scope:)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
class CreateWithOnboarding
|
|
4
|
+
include ::Booth::MethodObject
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
option :username
|
|
8
|
+
option :allowed_modes
|
|
9
|
+
option :scope, default: -> { :default }
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
creation = ::Booth::Credentials::Create.call(username:, allowed_modes:, scope:)
|
|
13
|
+
|
|
14
|
+
creation.on_success do
|
|
15
|
+
onboarding = creation.credential.create_onboarding!
|
|
16
|
+
|
|
17
|
+
return Tron.success :credential_with_onboarding_created,
|
|
18
|
+
credential: creation.credential,
|
|
19
|
+
onboarding:
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
creation
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
class FindByUsername
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
option :username
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
do_check_username_syntax
|
|
11
|
+
.on_success { do_find_credential }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def do_check_username_syntax
|
|
17
|
+
checking = ::Booth::Syntaxes::Username.call(username)
|
|
18
|
+
@normalized_username = checking.normalized_username
|
|
19
|
+
@normalized_invalid_username = checking.normalized_invalid_username
|
|
20
|
+
|
|
21
|
+
checking
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def do_find_credential
|
|
25
|
+
if credential
|
|
26
|
+
Tron.success :found_existing_credential, credential: credential,
|
|
27
|
+
normalized_username: @normalized_username,
|
|
28
|
+
normalized_invalid_username: @normalized_invalid_username
|
|
29
|
+
else
|
|
30
|
+
Tron.failure :credential_not_found, normalized_username: @normalized_username,
|
|
31
|
+
normalized_invalid_username: @normalized_invalid_username,
|
|
32
|
+
public_message: I18n.t('booth.unknown_username')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Helpers
|
|
37
|
+
|
|
38
|
+
def credential
|
|
39
|
+
return @credential if defined?(@credential)
|
|
40
|
+
|
|
41
|
+
@credential = ::Booth::Models::Credential.find_by(username: @normalized_username)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
class Mode
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
|
|
6
|
+
def initialize(credential)
|
|
7
|
+
@credential = credential
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# OTP
|
|
11
|
+
|
|
12
|
+
def otp_addable?
|
|
13
|
+
::Booth::Credentials::Modes::OtpAddable.call(credential)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def otp_changeable?
|
|
17
|
+
::Booth::Credentials::Modes::OtpChangeable.call(credential)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def otp_removable?
|
|
21
|
+
::Booth::Credentials::Modes::OtpRemovable.call(credential)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def otp_manageable?
|
|
25
|
+
::Booth::Credentials::Modes::OtpManageable.call(credential)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Password
|
|
29
|
+
|
|
30
|
+
def password_addable?
|
|
31
|
+
::Booth::Credentials::Modes::PasswordAddable.call(credential)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def password_changeable?
|
|
35
|
+
::Booth::Credentials::Modes::PasswordChangeable.call(credential)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def password_removable?
|
|
39
|
+
::Booth::Credentials::Modes::PasswordRemovable.call(credential)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def password_removal_requires_user_verifiable_webauth?
|
|
43
|
+
::Booth::Credentials::Modes::PasswordRemovalRequiresUserVerifiableWebauth.call(credential)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def password_manageable?
|
|
47
|
+
::Booth::Credentials::Modes::PasswordManageable.call(credential)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Webauth
|
|
51
|
+
|
|
52
|
+
def webauth_addable?
|
|
53
|
+
::Booth::Credentials::Modes::WebauthAddable.call(credential)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def webauth_removable?
|
|
57
|
+
::Booth::Credentials::Modes::WebauthRemovable.call(credential)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def webauth_manageable?
|
|
61
|
+
::Booth::Credentials::Modes::WebauthManageable.call(credential)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
attr_reader :credential
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
module Modes
|
|
4
|
+
class OtpAddable
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :credential
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
!why_not?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def why_not?
|
|
16
|
+
return :first_time_credential if credential.mode_first_time?
|
|
17
|
+
return :already_exists if credential.mode_username_password_and_otp?
|
|
18
|
+
return :otp_not_allowed unless credential.allows_mode_username_password_and_otp?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
module Modes
|
|
4
|
+
class OtpChangeable
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :credential
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
!why_not?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def why_not?
|
|
16
|
+
return :first_time_credential if credential.mode_first_time?
|
|
17
|
+
return :no_otp_in_use unless credential.mode_username_password_and_otp?
|
|
18
|
+
return :otp_deprecated unless credential.allows_mode_username_password_and_otp?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
module Modes
|
|
4
|
+
class OtpManageable
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :credential
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
::Booth::Credentials::Modes::OtpAddable.call(credential) ||
|
|
11
|
+
::Booth::Credentials::Modes::OtpChangeable.call(credential) ||
|
|
12
|
+
::Booth::Credentials::Modes::OtpRemovable.call(credential)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
module Modes
|
|
4
|
+
class OtpRemovable
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :credential
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
!why_not?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def why_not?
|
|
16
|
+
return :first_time_credential if credential.mode_first_time?
|
|
17
|
+
return :otp_not_in_use unless credential.mode_username_password_and_otp?
|
|
18
|
+
return :otp_required unless credential.allows_mode_username_and_password?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
module Modes
|
|
4
|
+
class PasswordAddable
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :credential
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
!why_not?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def why_not?
|
|
16
|
+
return :first_time_credential_is_passwordless if credential.mode_first_time? &&
|
|
17
|
+
!credential.mode_username_and_password? &&
|
|
18
|
+
!credential.mode_username_password_and_otp? &&
|
|
19
|
+
!credential.mode_username_password_and_webauth?
|
|
20
|
+
return :password_with_username_in_use if credential.mode_username_and_password?
|
|
21
|
+
return :password_with_otp_in_use if credential.mode_username_password_and_otp?
|
|
22
|
+
return :password_with_webauth_in_use if credential.mode_username_password_and_webauth?
|
|
23
|
+
|
|
24
|
+
return :not_allowed_with_webauth unless credential.allows_mode_username_password_and_webauth?
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
module Modes
|
|
4
|
+
class PasswordChangeable
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
include ::Booth::MethodObject
|
|
7
|
+
|
|
8
|
+
param :credential
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
!why_not?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def why_not?
|
|
17
|
+
return :first_time_credential if credential.mode_first_time?
|
|
18
|
+
return :username_and_password_deprecated if credential.mode_username_and_password? &&
|
|
19
|
+
!credential.allows_mode_username_and_password?
|
|
20
|
+
return :username_and_password_and_otp_deprecated if credential.mode_username_password_and_otp? &&
|
|
21
|
+
!credential.allows_mode_username_password_and_otp?
|
|
22
|
+
return :username_password_and_webauth_deprecated if credential.mode_username_password_and_webauth? &&
|
|
23
|
+
!credential.allows_mode_username_password_and_webauth?
|
|
24
|
+
return :password_not_in_use unless credential.mode_username_and_password? ||
|
|
25
|
+
credential.mode_username_password_and_otp? ||
|
|
26
|
+
credential.mode_username_password_and_webauth?
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
module Modes
|
|
4
|
+
class PasswordManageable
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :credential
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
::Booth::Credentials::Modes::PasswordAddable.call(credential) ||
|
|
11
|
+
::Booth::Credentials::Modes::PasswordChangeable.call(credential) ||
|
|
12
|
+
::Booth::Credentials::Modes::PasswordRemovable.call(credential)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
module Modes
|
|
4
|
+
class PasswordRemovable
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
include ::Booth::MethodObject
|
|
7
|
+
|
|
8
|
+
param :credential
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
!why_not?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def why_not?
|
|
17
|
+
return :first_time_credential if credential.mode_first_time?
|
|
18
|
+
return :webauth_needed unless credential.mode_username_password_and_webauth?
|
|
19
|
+
return :not_downgradable unless credential.allows_mode_username_and_webauth?
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
module Modes
|
|
4
|
+
class PasswordRemovalRequiresUserVerifiableWebauth
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
include ::Booth::MethodObject
|
|
7
|
+
|
|
8
|
+
param :credential
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
::Booth::Credentials::Modes::PasswordRemovable.call(credential) == :need_user_verifiable_authenticators
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
module Modes
|
|
4
|
+
class WebauthAddable
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :credential
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
!why_not?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def why_not?
|
|
16
|
+
return :first_time_credential_needs_password if credential.mode_first_time? &&
|
|
17
|
+
!credential.allows_mode_username_and_webauth?
|
|
18
|
+
|
|
19
|
+
return :webauth_not_allowed unless credential.mode_first_time? ||
|
|
20
|
+
credential.allows_mode_username_password_and_webauth? ||
|
|
21
|
+
credential.allows_mode_username_and_webauth?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
module Modes
|
|
4
|
+
class WebauthManageable
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :credential
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
::Booth::Credentials::Modes::WebauthAddable.call(credential) ||
|
|
11
|
+
::Booth::Credentials::Modes::WebauthRemovable.call(credential)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
module Modes
|
|
4
|
+
class WebauthRemovable
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
|
|
7
|
+
param :credential
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
return true if credential.authenticators.registered_scope.size > 1
|
|
11
|
+
|
|
12
|
+
!why_not?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def why_not?
|
|
18
|
+
return :first_time_credential if credential.mode_first_time?
|
|
19
|
+
return :webauth_not_secondary_factor unless credential.mode_username_password_and_webauth?
|
|
20
|
+
return :webauth_as_secondary_required unless credential.allows_mode_username_and_password?
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
class OtpAuthentication
|
|
4
|
+
include ::Booth::MethodObject
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
option :credential
|
|
8
|
+
option :code
|
|
9
|
+
option :ip
|
|
10
|
+
option :agent
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
do_check_otp_code_syntax
|
|
14
|
+
.on_success { do_check_cooldown }
|
|
15
|
+
.on_success { do_check_code }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def do_check_otp_code_syntax
|
|
21
|
+
check = ::Booth::Syntaxes::Otp.call(code)
|
|
22
|
+
return check if check.failure?
|
|
23
|
+
|
|
24
|
+
@normalized_otp_code = check.normalized_otp
|
|
25
|
+
check
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def do_check_cooldown
|
|
29
|
+
::Booth::Cooldowns::Otp.call credential:
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def do_check_code
|
|
33
|
+
return correct_code! if credential.authenticate_otp(@normalized_otp_code)
|
|
34
|
+
|
|
35
|
+
wrong_code!
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def correct_code!
|
|
39
|
+
debug { 'The provided OTP is correct' }
|
|
40
|
+
::Booth::Audits::Register::CorrectOtp.call credential: credential,
|
|
41
|
+
ip: ip,
|
|
42
|
+
agent: agent
|
|
43
|
+
Tron.success :correct_otp
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def wrong_code!
|
|
47
|
+
debug { "The provided OTP #{@normalized_otp_code} is wrong. It should have been #{credential.otp_code}" }
|
|
48
|
+
::Booth::Audits::Register::WrongOtp.call credential: credential,
|
|
49
|
+
ip: ip,
|
|
50
|
+
agent: agent
|
|
51
|
+
|
|
52
|
+
cooldown = ::Booth::Cooldowns::Otp.call credential: credential
|
|
53
|
+
return Tron.failure(:wrong_password, public_message: I18n.t('booth.wrong_otp')) if cooldown.success?
|
|
54
|
+
|
|
55
|
+
cooldown
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
class PasswordAuthentication
|
|
4
|
+
include ::Booth::MethodObject
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
option :credential
|
|
8
|
+
option :password
|
|
9
|
+
option :ip
|
|
10
|
+
option :agent
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
do_check_blank_password
|
|
14
|
+
.on_success { do_check_too_short_password }
|
|
15
|
+
.on_success { do_check_cooldown }
|
|
16
|
+
.on_success { do_check_password }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def do_check_blank_password
|
|
22
|
+
return Tron.success :password_is_present if password.present?
|
|
23
|
+
|
|
24
|
+
debug { 'The provided password is blank' }
|
|
25
|
+
Tron.failure :blank_password, public_message: I18n.t('booth.blank_password')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def do_check_too_short_password
|
|
29
|
+
return Tron.success :password_is_long_enough if password.to_s.length >= credential.class.password_minlength
|
|
30
|
+
|
|
31
|
+
debug { 'The provided password is too short' }
|
|
32
|
+
Tron.failure :short_password, public_message: I18n.t('booth.short_password',
|
|
33
|
+
minlength: credential.class.password_minlength)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def do_check_cooldown
|
|
37
|
+
cooldown = ::Booth::Cooldowns::Password.call(ip:, credential:)
|
|
38
|
+
return cooldown if cooldown.success?
|
|
39
|
+
|
|
40
|
+
cooldown.public_message.prepend "#{I18n.t('booth.attempt_was_ignored')} "
|
|
41
|
+
cooldown
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def do_check_password
|
|
45
|
+
return correct_pasword! if credential.authenticate(password)
|
|
46
|
+
|
|
47
|
+
wrong_password!
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def correct_pasword!
|
|
51
|
+
debug { 'The provided password is correct' }
|
|
52
|
+
::Booth::Audits::Register::CorrectPassword.call credential: credential,
|
|
53
|
+
ip: ip,
|
|
54
|
+
agent: agent
|
|
55
|
+
Tron.success :correct_password
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def wrong_password!
|
|
59
|
+
debug { 'The provided password is wrong' }
|
|
60
|
+
::Booth::Audits::Register::WrongPassword.call credential: credential,
|
|
61
|
+
ip: ip,
|
|
62
|
+
agent: agent
|
|
63
|
+
|
|
64
|
+
cooldown = ::Booth::Cooldowns::Password.call ip: ip, credential: credential
|
|
65
|
+
return Tron.failure(:wrong_password, public_message: I18n.t('booth.wrong_password')) if cooldown.success?
|
|
66
|
+
|
|
67
|
+
cooldown.public_message.prepend "#{I18n.t('booth.wrong_password')} "
|
|
68
|
+
cooldown
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Credentials
|
|
3
|
+
# See https://github.com/cedarcode/webauthn-rails-demo-app/blob/d9b73e20a7272e8d9f7a26c48ec49e5100295939/app/controllers/sessions_controller.rb#L7-L23
|
|
4
|
+
class WebauthChallenge
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
include ::Booth::MethodObject
|
|
7
|
+
|
|
8
|
+
option :credential
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
return Tron.failure :missing_credential, challenge: nil if credential.nil?
|
|
12
|
+
return Tron.failure :no_known_authenticators, challenge: nil if credential.registered_authenticator_ids.blank?
|
|
13
|
+
|
|
14
|
+
Tron.success :here_is_your_challenge, options_for_get: options_for_get.to_json,
|
|
15
|
+
challenge: options_for_get.challenge
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def options_for_get
|
|
21
|
+
@options_for_get ||= ::Booth::Webauth::OptionsForGet.call(
|
|
22
|
+
allowed_device_ids: credential.registered_authenticator_ids,
|
|
23
|
+
requires_user_verification: credential.requires_user_verification?
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/booth/engine.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
class Engine < ::Rails::Engine
|
|
3
|
+
initializer 'booth.add_manifest' do |app|
|
|
4
|
+
app.config.assets.precompile << 'booth_manifest.js'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
initializer 'booth.insert_warden_middleware' do |app|
|
|
8
|
+
app.config.middleware.insert_after ::ActionDispatch::Flash, ::Warden::Manager do |manager|
|
|
9
|
+
manager.intercept_401 = false
|
|
10
|
+
manager.serialize_into_session { ::Booth::Hooks::SerializeIntoSession.call(_1) }
|
|
11
|
+
manager.serialize_from_session { ::Booth::Hooks::SerializeFromSession.call(_1) }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
initializer 'booth.register_warden_hooks' do
|
|
16
|
+
::Warden::Manager.after_fetch do |passport, warden, options|
|
|
17
|
+
::Booth::Hooks::AfterFetch.call passport:, warden:, options:
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
::Warden::Manager.before_logout do |passport, warden, options|
|
|
21
|
+
::Booth::Hooks::BeforeLogout.call passport:, warden:, options:
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|