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,54 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Requests
|
|
3
|
+
module Storages
|
|
4
|
+
class Otp
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
delegate :reset, :timed_out?, :lifespan, :seconds_until_auto_reset, to: :session
|
|
8
|
+
|
|
9
|
+
def initialize(session:)
|
|
10
|
+
@session = session
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# -------
|
|
14
|
+
# Getters
|
|
15
|
+
# -------
|
|
16
|
+
|
|
17
|
+
def secret_key
|
|
18
|
+
session[:secret_key]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def secret_key_has_been_seen?
|
|
22
|
+
!!session[:secret_key_has_been_seen]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def secret_key_recently_changed?
|
|
26
|
+
!!session[:secret_key_recently_changed]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# -------
|
|
30
|
+
# Setters
|
|
31
|
+
# -------
|
|
32
|
+
|
|
33
|
+
def generate_secret_key!
|
|
34
|
+
debug { 'Generating secret OTP key for registration' }
|
|
35
|
+
session[:secret_key] = ::Booth::Models::Credential.otp_random_secret
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def secret_key_has_been_seen!
|
|
39
|
+
debug { 'Remembering that the secret OTP key has been registered' }
|
|
40
|
+
session[:secret_key_has_been_seen] = true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def secret_key_recently_changed!
|
|
44
|
+
reset
|
|
45
|
+
session[:secret_key_recently_changed] = true
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
attr_reader :session
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Requests
|
|
3
|
+
module Storages
|
|
4
|
+
class Password
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
delegate :reset, :timed_out?, :lifespan, :seconds_until_auto_reset, to: :session
|
|
8
|
+
|
|
9
|
+
def initialize(session:)
|
|
10
|
+
@session = session
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# -------
|
|
14
|
+
# Getters
|
|
15
|
+
# -------
|
|
16
|
+
|
|
17
|
+
def password_digest
|
|
18
|
+
session[:password_digest]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# To show a reminder for the user to logout other devices after password change.
|
|
22
|
+
# This value either times out or is deleted at read-confirmation by the user.
|
|
23
|
+
def password_recently_changed?
|
|
24
|
+
!!session[:password_recently_changed]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# -------
|
|
28
|
+
# Setters
|
|
29
|
+
# -------
|
|
30
|
+
|
|
31
|
+
def password_digest=(new_password_digest)
|
|
32
|
+
debug { 'Remembering new password digest' }
|
|
33
|
+
session[:password_digest] = new_password_digest
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def password_recently_changed!
|
|
37
|
+
debug { 'Remembering that the password was recently changed' }
|
|
38
|
+
session[:password_recently_changed] = true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
attr_reader :session
|
|
44
|
+
|
|
45
|
+
delegate :scope, to: :session, private: true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Requests
|
|
3
|
+
module Storages
|
|
4
|
+
class PasswordReset
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
delegate :reset, :timed_out?, :lifespan, :seconds_until_auto_reset, to: :session
|
|
8
|
+
|
|
9
|
+
def initialize(session:)
|
|
10
|
+
@session = session
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# -------
|
|
14
|
+
# Getters
|
|
15
|
+
# -------
|
|
16
|
+
|
|
17
|
+
def email
|
|
18
|
+
session[:email]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# -------
|
|
22
|
+
# Setters
|
|
23
|
+
# -------
|
|
24
|
+
|
|
25
|
+
def email=(new_email)
|
|
26
|
+
session[:email] = new_email
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
attr_reader :session
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Requests
|
|
3
|
+
module Storages
|
|
4
|
+
class Recovery
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
delegate :reset, :timed_out?, :lifespan, :seconds_until_auto_reset, to: :session
|
|
8
|
+
|
|
9
|
+
def initialize(session:)
|
|
10
|
+
@session = session
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# -------
|
|
14
|
+
# Getters
|
|
15
|
+
# -------
|
|
16
|
+
|
|
17
|
+
def email
|
|
18
|
+
session[:email]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# -------
|
|
22
|
+
# Setters
|
|
23
|
+
# -------
|
|
24
|
+
|
|
25
|
+
def email=(new_email)
|
|
26
|
+
session[:email] = new_email
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
attr_reader :session
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Requests
|
|
3
|
+
module Storages
|
|
4
|
+
class Registration
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
delegate :reset, to: :session
|
|
8
|
+
|
|
9
|
+
def initialize(session:)
|
|
10
|
+
@session = session
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def username
|
|
14
|
+
session[:username]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def username=(new_username)
|
|
18
|
+
session[:username] = new_username
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
attr_reader :session
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Requests
|
|
3
|
+
module Storages
|
|
4
|
+
class Webauth
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
delegate :reset, :timed_out?, :lifespan, :seconds_until_auto_reset, to: :session
|
|
8
|
+
|
|
9
|
+
def initialize(session:)
|
|
10
|
+
@session = session
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# -------
|
|
14
|
+
# Getters
|
|
15
|
+
# -------
|
|
16
|
+
|
|
17
|
+
def authenticator
|
|
18
|
+
return @authenticator if defined?(@authenticator)
|
|
19
|
+
return if session[:authenticator_id].blank?
|
|
20
|
+
|
|
21
|
+
@authenticator = ::Booth::Models::Authenticator.find_by(id: session[:authenticator_id])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# -------
|
|
25
|
+
# Setters
|
|
26
|
+
# -------
|
|
27
|
+
|
|
28
|
+
def authenticator_id=(new_authenticator_id)
|
|
29
|
+
session[:authenticator_id] = new_authenticator_id
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
attr_reader :session
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Requests
|
|
3
|
+
class Sudo
|
|
4
|
+
include ::Booth::Logging
|
|
5
|
+
|
|
6
|
+
def initialize(scope:, request:)
|
|
7
|
+
@scope = scope
|
|
8
|
+
@request = request
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def mode
|
|
12
|
+
request.authentication.mode.to_sym
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def lifespan
|
|
16
|
+
::Booth.config.interaction_timeout
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Guards
|
|
20
|
+
|
|
21
|
+
def guard_with_password
|
|
22
|
+
raise unless block_given?
|
|
23
|
+
return if password?
|
|
24
|
+
|
|
25
|
+
debug { 'You need password sudo' }
|
|
26
|
+
public_message = I18n.t('booth.password_sudo_timeout', lifespan_minutes: (lifespan / 60))
|
|
27
|
+
yield Tron.success(:password_sudo_needed, step: :sudo, public_message:)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def guard_with_otp
|
|
31
|
+
raise unless block_given?
|
|
32
|
+
return if otp?
|
|
33
|
+
|
|
34
|
+
debug { 'You need OTP sudo' }
|
|
35
|
+
public_message = I18n.t('booth.otp_sudo_timeout', lifespan_minutes: (lifespan / 60))
|
|
36
|
+
yield Tron.success(:otp_sudo_needed, step: :sudo, public_message:)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def guard_with_webauth
|
|
40
|
+
raise unless block_given?
|
|
41
|
+
return if webauth?
|
|
42
|
+
|
|
43
|
+
debug { 'You need Webauth sudo' }
|
|
44
|
+
public_message = I18n.t('booth.webauth_sudo_timeout', lifespan_minutes: (lifespan / 60))
|
|
45
|
+
yield Tron.success(:webauth_sudo_needed, step: :sudo, public_message:)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Getters
|
|
49
|
+
|
|
50
|
+
def password?
|
|
51
|
+
return true if session[:password].to_i > lifespan.ago.to_i
|
|
52
|
+
|
|
53
|
+
session[:password] = nil
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def otp?
|
|
58
|
+
return true if session[:otp].to_i > lifespan.ago.to_i
|
|
59
|
+
|
|
60
|
+
session[:otp] = nil
|
|
61
|
+
false
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def webauth?
|
|
65
|
+
return true if session[:webauth].to_i > lifespan.ago.to_i
|
|
66
|
+
|
|
67
|
+
session[:webauth] = nil
|
|
68
|
+
false
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def webauthn_challenge
|
|
72
|
+
session[:webauthn_challenge].presence
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Setters
|
|
76
|
+
|
|
77
|
+
def password!
|
|
78
|
+
debug { "Remembering sudo via password has been granted in scope #{scope}" }
|
|
79
|
+
session[:password] = Time.current.to_i
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def otp!
|
|
83
|
+
debug { "Remembering sudo via OTP has been granted in scope #{scope}" }
|
|
84
|
+
session[:otp] = Time.current.to_i
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def webauth!
|
|
88
|
+
debug { "Remembering sudo via WebAuth has been granted in scope #{scope}" }
|
|
89
|
+
session[:webauth] = Time.current.to_i
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def webauthn_challenge=(new_challenge)
|
|
93
|
+
if new_challenge
|
|
94
|
+
debug { "Persisting webauth challenge #{new_challenge.inspect} in sudo session for scope #{scope.inspect}" }
|
|
95
|
+
else
|
|
96
|
+
debug { "Removing webauth challenge from sudo session for scope #{scope.inspect}" }
|
|
97
|
+
end
|
|
98
|
+
session[:webauthn_challenge] = new_challenge.presence
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
attr_reader :scope, :request
|
|
104
|
+
|
|
105
|
+
def session
|
|
106
|
+
request.session(namespace: :sudo)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Routes
|
|
3
|
+
class Userland
|
|
4
|
+
include ::Booth::MethodObject
|
|
5
|
+
|
|
6
|
+
option :response_path, default: -> {}
|
|
7
|
+
option :self_registration
|
|
8
|
+
option :self_recovery
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
result = [mandatory_routes]
|
|
12
|
+
result.push self_registration_routes if self_registration
|
|
13
|
+
result.push self_recovery_routes if self_recovery
|
|
14
|
+
result.join
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def response_path_with_fallback
|
|
20
|
+
return ':response' if response_path.blank?
|
|
21
|
+
|
|
22
|
+
# Just to avoid potential syntax errors, we sanitize the variable little bit.
|
|
23
|
+
response_path.to_sym.to_s.inspect
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# ---------
|
|
27
|
+
# Templates
|
|
28
|
+
# ---------
|
|
29
|
+
|
|
30
|
+
# There is probably no reason to avoid any of these routes.
|
|
31
|
+
def mandatory_routes
|
|
32
|
+
<<-RUBY
|
|
33
|
+
# Registering credentials for the first time, such as Hardware keys.
|
|
34
|
+
resources :onboardings, only: %i[show update]
|
|
35
|
+
|
|
36
|
+
# Login
|
|
37
|
+
resource :login, only: %i[new create destroy]
|
|
38
|
+
resource :response, only: %i[show update], path: #{response_path_with_fallback}
|
|
39
|
+
|
|
40
|
+
# Self-service portal
|
|
41
|
+
resources :sessions, only: %i[index show destroy] do
|
|
42
|
+
delete :destroy_all_others, on: :collection
|
|
43
|
+
end
|
|
44
|
+
resource :password, only: %i[show edit update destroy] do
|
|
45
|
+
collection do
|
|
46
|
+
get :remove
|
|
47
|
+
post :sudo
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
resource :otp, only: %i[show edit update destroy] do
|
|
51
|
+
post :sudo, on: :collection
|
|
52
|
+
end
|
|
53
|
+
resources :webauths, only: %i[index new create destroy] do
|
|
54
|
+
post :sudo, on: :collection
|
|
55
|
+
end
|
|
56
|
+
RUBY
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# If users create their own accounts, you will need these routes.
|
|
60
|
+
def self_registration_routes
|
|
61
|
+
<<-RUBY
|
|
62
|
+
resources :registrations, only: %i[new create]
|
|
63
|
+
RUBY
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# If users can recover lost credentials (username/password) via email, you need these routes.
|
|
67
|
+
def self_recovery_routes
|
|
68
|
+
<<-RUBY
|
|
69
|
+
resources :recoveries, only: %i[new create] do
|
|
70
|
+
get :check_your_mail, on: :collection
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
resources :password_resets, only: %i[new create show update] do
|
|
74
|
+
get :check_your_mail, on: :collection
|
|
75
|
+
end
|
|
76
|
+
RUBY
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Sessions
|
|
3
|
+
class CreateAndLogin
|
|
4
|
+
include ::Booth::MethodObject
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
option :credential
|
|
8
|
+
option :request, ::Booth::Request
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
if request.scope != credential.scope.to_sym
|
|
12
|
+
debug { "Request has scope #{request.scope.inspect} but Credential has #{credential.scope.to_sym.inspect}" }
|
|
13
|
+
raise 'Not performing login because something in your setup is wrong.'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
request.authentication
|
|
17
|
+
.login(session: create_session)
|
|
18
|
+
|
|
19
|
+
# Consume everything that led up to this authentication to avoid re-authentication.
|
|
20
|
+
# If we don't reset the cookie here, the "remote login flow" could log you right back in.
|
|
21
|
+
login_storage.reset
|
|
22
|
+
registration_storage.reset
|
|
23
|
+
|
|
24
|
+
Tron.success :session_created_and_logged_in, return_path: request.return_path
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def create_session
|
|
30
|
+
debug { "Creating new Session for credential ID #{credential.id}" }
|
|
31
|
+
|
|
32
|
+
::Booth::Models::Session.create! credential:,
|
|
33
|
+
agent: request.agent,
|
|
34
|
+
most_recent_ip: request.ip
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def login_storage
|
|
38
|
+
request.storage.login
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def registration_storage
|
|
42
|
+
request.storage.registration
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Sessions
|
|
3
|
+
class HistoricalLocations
|
|
4
|
+
include ::Booth::MethodObject
|
|
5
|
+
|
|
6
|
+
param :session
|
|
7
|
+
|
|
8
|
+
# TODO: Show IPs for cities that are different from the most recent IP.
|
|
9
|
+
def call
|
|
10
|
+
return if historical_locations.values.blank?
|
|
11
|
+
|
|
12
|
+
historical_locations.values.reject { _1 == location }.uniq.sort.join(', ').presence
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
delegate :location, :historical_locations, to: :session, private: true
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Sessions
|
|
3
|
+
class Index
|
|
4
|
+
include ::Booth::MethodObject
|
|
5
|
+
|
|
6
|
+
option :credential_id
|
|
7
|
+
option :current_session_id
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
# Booth currently doesn't support pagination.
|
|
11
|
+
# To avoid an attacking vector that could bring our database down,
|
|
12
|
+
# we simply pull the handbrake when trying to fetch too many sessions.
|
|
13
|
+
# Nobody should have that many active concurrent sessions.
|
|
14
|
+
raise "Too many sessions for Credential ID #{credential_id}" if base_scope.count > max_allowed_sessions
|
|
15
|
+
|
|
16
|
+
base_scope.map do |session|
|
|
17
|
+
::Booth::ToStruct.call(exposed_attributes(session))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def exposed_attributes(session)
|
|
24
|
+
result = { is_current: current_session_id == session.id }
|
|
25
|
+
|
|
26
|
+
exposed_attribute_names.each do |attribute|
|
|
27
|
+
result[attribute] = session.public_send(attribute)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
result
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def exposed_attribute_names
|
|
34
|
+
%i[id
|
|
35
|
+
activity_at
|
|
36
|
+
created_at
|
|
37
|
+
agent
|
|
38
|
+
most_recent_ip
|
|
39
|
+
historical_ips
|
|
40
|
+
location
|
|
41
|
+
historical_location_names
|
|
42
|
+
browser_name
|
|
43
|
+
platform_name
|
|
44
|
+
browser_image_path
|
|
45
|
+
platform_image_path]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def base_scope
|
|
49
|
+
::Booth::Models::Session.active_scope
|
|
50
|
+
.sorted_scope
|
|
51
|
+
.owned_by_scope(credential_id:)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def max_allowed_sessions
|
|
55
|
+
500
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Sessions
|
|
3
|
+
class Revoke
|
|
4
|
+
include ::Booth::MethodObject
|
|
5
|
+
include ::Booth::Logging
|
|
6
|
+
|
|
7
|
+
option :credential_id
|
|
8
|
+
option :session_id
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
do_check_id_syntax
|
|
12
|
+
.on_success { do_revoke_session }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def do_check_id_syntax
|
|
18
|
+
::Booth::Syntaxes::Uuid.call(credential_id, raise_if_invalid: true)
|
|
19
|
+
::Booth::Syntaxes::Uuid.call(session_id, raise_if_invalid: true)
|
|
20
|
+
|
|
21
|
+
Tron.success :valid_session_id_syntax
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def do_revoke_session
|
|
25
|
+
unless session
|
|
26
|
+
debug { "Session #{session_id.inspect} not found for credential #{credential_id.inspect}" }
|
|
27
|
+
return Tron.success :session_not_found
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if session.revoked_at.present?
|
|
31
|
+
debug { "Session #{session.id.inspect} is already revoked." }
|
|
32
|
+
return Tron.success :already_revoked,
|
|
33
|
+
public_message: I18n.t('booth.session_revoked', ip: session.most_recent_ip)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
debug { "Revoking Session #{session.id.inspect}" }
|
|
37
|
+
session.update! revoked_at: Time.current,
|
|
38
|
+
revoke_reason: :manual
|
|
39
|
+
|
|
40
|
+
Tron.success :session_revoked, public_message: I18n.t('booth.session_revoked', ip: session.most_recent_ip)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def session
|
|
44
|
+
return @session if defined?(@session)
|
|
45
|
+
|
|
46
|
+
@session = ::Booth::Models::Session.where(credential_id:)
|
|
47
|
+
.find_by(id: session_id)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Sessions
|
|
3
|
+
class RevokeAllOthers
|
|
4
|
+
include ::Booth::MethodObject
|
|
5
|
+
|
|
6
|
+
option :credential_id
|
|
7
|
+
option :surviving_session_id
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
do_check_id_syntax
|
|
11
|
+
.on_success { do_revoke_all_other_session }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def do_check_id_syntax
|
|
17
|
+
::Booth::Syntaxes::Uuid.call(credential_id, raise_if_invalid: true)
|
|
18
|
+
::Booth::Syntaxes::Uuid.call(surviving_session_id, raise_if_invalid: true)
|
|
19
|
+
|
|
20
|
+
Tron.success :valid_session_id_syntax
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def do_revoke_all_other_session
|
|
24
|
+
if sessions.blank?
|
|
25
|
+
return Tron.success :nothing_to_revoke, public_message: I18n.t('booth.all_other_sessions_revoked')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Preferring speed. Because if there are many we don't want the page to hang.
|
|
29
|
+
sessions.update_all revoked_at: Time.current,
|
|
30
|
+
revoke_reason: :manual_all_others
|
|
31
|
+
|
|
32
|
+
Tron.success :other_sessions_revoked, public_message: I18n.t('booth.all_other_sessions_revoked')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def sessions
|
|
36
|
+
return @sessions if defined?(@sessions)
|
|
37
|
+
|
|
38
|
+
@sessions = ::Booth::Models::Session.where(credential_id:)
|
|
39
|
+
.where.not(id: surviving_session_id)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Booth
|
|
2
|
+
module Sessions
|
|
3
|
+
# A Passport is an immutable representation of your current browser-session.
|
|
4
|
+
class ToPassport
|
|
5
|
+
include ::Booth::MethodObject
|
|
6
|
+
include ::Booth::Logging
|
|
7
|
+
|
|
8
|
+
param :session
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
::Booth::ToStruct.call(attributes)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
delegate :credential, to: :session, private: true
|
|
17
|
+
|
|
18
|
+
def attributes
|
|
19
|
+
{
|
|
20
|
+
id: session.id,
|
|
21
|
+
username: credential.username,
|
|
22
|
+
credential_id: credential.id,
|
|
23
|
+
mode:,
|
|
24
|
+
incognito_credential_id: session.incognito_credential_id,
|
|
25
|
+
revoked_at: session.revoked_at,
|
|
26
|
+
onboarding_secret_key:,
|
|
27
|
+
is:,
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def is
|
|
32
|
+
::Booth::Credentials::Mode.new(credential)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def mode
|
|
36
|
+
credential.mode.to_sym
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def onboarding_secret_key
|
|
40
|
+
return unless credential.mode_first_time?
|
|
41
|
+
|
|
42
|
+
# If a first-time credential is in a logged in state,
|
|
43
|
+
# that means this credential was just self-registered.
|
|
44
|
+
|
|
45
|
+
# In that case, reveal the onboarding secret key,
|
|
46
|
+
# so that the end-user may choose a login method for the first time.
|
|
47
|
+
credential.onboarding&.secret_key
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|