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.
Files changed (285) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +4 -0
  3. data/LICENSE.md +22 -0
  4. data/README.md +372 -0
  5. data/app/assets/config/booth_manifest.js +15 -0
  6. data/app/assets/images/booth/browsers/README.md +2 -0
  7. data/app/assets/images/booth/browsers/chrome.svg +1 -0
  8. data/app/assets/images/booth/browsers/edge.svg +1 -0
  9. data/app/assets/images/booth/browsers/firefox.svg +1 -0
  10. data/app/assets/images/booth/browsers/internet_explorer.svg +1 -0
  11. data/app/assets/images/booth/browsers/opera.svg +1 -0
  12. data/app/assets/images/booth/browsers/safari.svg +1 -0
  13. data/app/assets/images/booth/browsers/unknown.svg +1 -0
  14. data/app/assets/images/booth/platforms/README.md +2 -0
  15. data/app/assets/images/booth/platforms/android.svg +6 -0
  16. data/app/assets/images/booth/platforms/apple.svg +6 -0
  17. data/app/assets/images/booth/platforms/linux.svg +6 -0
  18. data/app/assets/images/booth/platforms/unknown.svg +1 -0
  19. data/app/assets/images/booth/platforms/windows.svg +6 -0
  20. data/app/assets/javascripts/booth/all.js +162 -0
  21. data/app/assets/javascripts/booth/all.js.map +1 -0
  22. data/app/assets/javascripts/booth/booth.ts +194 -0
  23. data/app/assets/javascripts/booth/webauthn-json.ts +99 -0
  24. data/config/locales/de.yml +84 -0
  25. data/config/locales/en.yml +79 -0
  26. data/lib/booth/adminland/credentials/create.rb +30 -0
  27. data/lib/booth/adminland/onboardings/create.rb +63 -0
  28. data/lib/booth/adminland/onboardings/destroy.rb +50 -0
  29. data/lib/booth/adminland/onboardings/find.rb +93 -0
  30. data/lib/booth/adminland/onboardings/index.rb +23 -0
  31. data/lib/booth/adminland/periodic_cleanup.rb +11 -0
  32. data/lib/booth/adminland/recoveries/consume.rb +70 -0
  33. data/lib/booth/adminland.rb +48 -0
  34. data/lib/booth/audits/register/added_otp.rb +22 -0
  35. data/lib/booth/audits/register/changed_otp.rb +22 -0
  36. data/lib/booth/audits/register/completed_onboarding.rb +22 -0
  37. data/lib/booth/audits/register/correct_otp.rb +42 -0
  38. data/lib/booth/audits/register/correct_password.rb +43 -0
  39. data/lib/booth/audits/register/logout.rb +22 -0
  40. data/lib/booth/audits/register/requested_password_reset.rb +22 -0
  41. data/lib/booth/audits/register/wrong_otp.rb +22 -0
  42. data/lib/booth/audits/register/wrong_password.rb +25 -0
  43. data/lib/booth/authenticators/confirm.rb +34 -0
  44. data/lib/booth/authenticators/credential_mode_after_confirmation.rb +25 -0
  45. data/lib/booth/authenticators/step.rb +19 -0
  46. data/lib/booth/concerns/action.rb +58 -0
  47. data/lib/booth/concerns/transition.rb +17 -0
  48. data/lib/booth/configuration.rb +116 -0
  49. data/lib/booth/configure.rb +37 -0
  50. data/lib/booth/contests/get.rb +36 -0
  51. data/lib/booth/contests/respond.rb +78 -0
  52. data/lib/booth/contests/set_for_login.rb +28 -0
  53. data/lib/booth/cooldowns/distance_of_time.rb +46 -0
  54. data/lib/booth/cooldowns/otp.rb +22 -0
  55. data/lib/booth/cooldowns/password.rb +44 -0
  56. data/lib/booth/cooldowns/password_reset.rb +24 -0
  57. data/lib/booth/cooldowns/strategies/exponential.rb +82 -0
  58. data/lib/booth/cooldowns/strategies/global.rb +62 -0
  59. data/lib/booth/cooldowns/strategies/result.rb +22 -0
  60. data/lib/booth/credentials/create.rb +28 -0
  61. data/lib/booth/credentials/create_with_onboarding.rb +26 -0
  62. data/lib/booth/credentials/find_by_username.rb +45 -0
  63. data/lib/booth/credentials/mode.rb +69 -0
  64. data/lib/booth/credentials/modes/otp_addable.rb +23 -0
  65. data/lib/booth/credentials/modes/otp_changeable.rb +23 -0
  66. data/lib/booth/credentials/modes/otp_manageable.rb +17 -0
  67. data/lib/booth/credentials/modes/otp_removable.rb +23 -0
  68. data/lib/booth/credentials/modes/password_addable.rb +29 -0
  69. data/lib/booth/credentials/modes/password_changeable.rb +31 -0
  70. data/lib/booth/credentials/modes/password_manageable.rb +17 -0
  71. data/lib/booth/credentials/modes/password_removable.rb +24 -0
  72. data/lib/booth/credentials/modes/password_removal_requires_user_verifiable_webauth.rb +16 -0
  73. data/lib/booth/credentials/modes/webauth_addable.rb +26 -0
  74. data/lib/booth/credentials/modes/webauth_manageable.rb +16 -0
  75. data/lib/booth/credentials/modes/webauth_removable.rb +25 -0
  76. data/lib/booth/credentials/otp_authentication.rb +59 -0
  77. data/lib/booth/credentials/password_authentication.rb +72 -0
  78. data/lib/booth/credentials/webauth_challenge.rb +28 -0
  79. data/lib/booth/engine.rb +25 -0
  80. data/lib/booth/errors.rb +86 -0
  81. data/lib/booth/geolocation.rb +20 -0
  82. data/lib/booth/hooks/after_fetch.rb +54 -0
  83. data/lib/booth/hooks/before_logout.rb +29 -0
  84. data/lib/booth/hooks/serialize_from_session.rb +24 -0
  85. data/lib/booth/hooks/serialize_into_session.rb +14 -0
  86. data/lib/booth/logger.rb +41 -0
  87. data/lib/booth/logging.rb +59 -0
  88. data/lib/booth/method_object.rb +73 -0
  89. data/lib/booth/mode.rb +22 -0
  90. data/lib/booth/models/application_record.rb +7 -0
  91. data/lib/booth/models/audit.rb +24 -0
  92. data/lib/booth/models/authenticator.rb +45 -0
  93. data/lib/booth/models/concerns/modeable.rb +50 -0
  94. data/lib/booth/models/concerns/otpable.rb +37 -0
  95. data/lib/booth/models/concerns/passwordable.rb +58 -0
  96. data/lib/booth/models/contest.rb +55 -0
  97. data/lib/booth/models/contests/scopes/recently_created.rb +23 -0
  98. data/lib/booth/models/contests/scopes/recently_responded.rb +32 -0
  99. data/lib/booth/models/credential.rb +61 -0
  100. data/lib/booth/models/onboarding.rb +61 -0
  101. data/lib/booth/models/password_reset.rb +41 -0
  102. data/lib/booth/models/recovery.rb +32 -0
  103. data/lib/booth/models/registration.rb +10 -0
  104. data/lib/booth/models/session.rb +47 -0
  105. data/lib/booth/models/user_agent.rb +50 -0
  106. data/lib/booth/modes/base.rb +25 -0
  107. data/lib/booth/modes/username_and_password.rb +7 -0
  108. data/lib/booth/modes/username_and_webauth.rb +7 -0
  109. data/lib/booth/modes/username_password_and_otp.rb +7 -0
  110. data/lib/booth/modes/username_password_and_webauth.rb +7 -0
  111. data/lib/booth/onboardings/find.rb +35 -0
  112. data/lib/booth/onboardings/propagate_to_credential.rb +63 -0
  113. data/lib/booth/onboardings/step.rb +68 -0
  114. data/lib/booth/password_resets/create.rb +57 -0
  115. data/lib/booth/password_resets/find.rb +36 -0
  116. data/lib/booth/password_resets/propagate_to_credential.rb +36 -0
  117. data/lib/booth/password_resets/step.rb +18 -0
  118. data/lib/booth/recoveries/create.rb +45 -0
  119. data/lib/booth/request.rb +106 -0
  120. data/lib/booth/requests/agent.rb +14 -0
  121. data/lib/booth/requests/authentication.rb +47 -0
  122. data/lib/booth/requests/ip.rb +28 -0
  123. data/lib/booth/requests/return_path.rb +34 -0
  124. data/lib/booth/requests/session.rb +106 -0
  125. data/lib/booth/requests/storage.rb +62 -0
  126. data/lib/booth/requests/storages/login.rb +108 -0
  127. data/lib/booth/requests/storages/otp.rb +54 -0
  128. data/lib/booth/requests/storages/password.rb +49 -0
  129. data/lib/booth/requests/storages/password_reset.rb +35 -0
  130. data/lib/booth/requests/storages/recovery.rb +35 -0
  131. data/lib/booth/requests/storages/registration.rb +27 -0
  132. data/lib/booth/requests/storages/webauth.rb +38 -0
  133. data/lib/booth/requests/sudo.rb +110 -0
  134. data/lib/booth/routes/userland.rb +80 -0
  135. data/lib/booth/sessions/create_and_login.rb +46 -0
  136. data/lib/booth/sessions/historical_locations.rb +18 -0
  137. data/lib/booth/sessions/index.rb +59 -0
  138. data/lib/booth/sessions/revoke.rb +51 -0
  139. data/lib/booth/sessions/revoke_all_others.rb +43 -0
  140. data/lib/booth/sessions/to_passport.rb +51 -0
  141. data/lib/booth/syntaxes/contest_code.rb +58 -0
  142. data/lib/booth/syntaxes/email.rb +97 -0
  143. data/lib/booth/syntaxes/ip.rb +37 -0
  144. data/lib/booth/syntaxes/otp.rb +57 -0
  145. data/lib/booth/syntaxes/scope.rb +21 -0
  146. data/lib/booth/syntaxes/scope_comparison.rb +28 -0
  147. data/lib/booth/syntaxes/secret_key.rb +64 -0
  148. data/lib/booth/syntaxes/username.rb +85 -0
  149. data/lib/booth/syntaxes/uuid.rb +23 -0
  150. data/lib/booth/test/helpers.rb +63 -0
  151. data/lib/booth/test/support/assert_all_partials_were_covered.rb +63 -0
  152. data/lib/booth/test/support/assert_logged_in.rb +49 -0
  153. data/lib/booth/test/support/assert_logged_out.rb +30 -0
  154. data/lib/booth/test/support/assert_partial.rb +29 -0
  155. data/lib/booth/test/support/force_login.rb +26 -0
  156. data/lib/booth/test/support/get_session_value.rb +35 -0
  157. data/lib/booth/test/support/otp_code_from_session.rb +30 -0
  158. data/lib/booth/test/support/soft_reset_session.rb +22 -0
  159. data/lib/booth/test/userland/logins/missing_authenticators.rb +72 -0
  160. data/lib/booth/test/userland/logins/missing_onboarding.rb +35 -0
  161. data/lib/booth/test/userland/logins/username_and_password.rb +40 -0
  162. data/lib/booth/test/userland/logins/username_and_webauth.rb +75 -0
  163. data/lib/booth/test/userland/logins/username_password_and_otp.rb +45 -0
  164. data/lib/booth/test/userland/logins/username_password_and_webauth.rb +86 -0
  165. data/lib/booth/test/userland/onboardings/already_logged_in.rb +64 -0
  166. data/lib/booth/test/userland/onboardings/otp.rb +63 -0
  167. data/lib/booth/test/userland/onboardings/password.rb +49 -0
  168. data/lib/booth/test/userland/onboardings/timeout.rb +47 -0
  169. data/lib/booth/test/userland/otps/manage.rb +86 -0
  170. data/lib/booth/test/userland/password_resets/reset.rb +102 -0
  171. data/lib/booth/test/userland.rb +38 -0
  172. data/lib/booth/test/webauthn/disable.rb +17 -0
  173. data/lib/booth/test/webauthn/enable.rb +19 -0
  174. data/lib/booth/test/webauthn/virtual_authenticators/create.rb +38 -0
  175. data/lib/booth/test/webauthn/virtual_authenticators/destroy.rb +20 -0
  176. data/lib/booth/test.rb +53 -0
  177. data/lib/booth/to_struct.rb +11 -0
  178. data/lib/booth/userland/extract_flash_messages.rb +35 -0
  179. data/lib/booth/userland/logins/create.rb +28 -0
  180. data/lib/booth/userland/logins/destroy.rb +37 -0
  181. data/lib/booth/userland/logins/new.rb +70 -0
  182. data/lib/booth/userland/logins/transitions/create/choose_username.rb +41 -0
  183. data/lib/booth/userland/logins/transitions/create/enter_otp.rb +70 -0
  184. data/lib/booth/userland/logins/transitions/create/skip_remotes.rb +24 -0
  185. data/lib/booth/userland/logins/transitions/create/verify_password.rb +70 -0
  186. data/lib/booth/userland/logins/transitions/create/webauth_authentication_initiation.rb +55 -0
  187. data/lib/booth/userland/logins/transitions/create/webauth_authentication_verification.rb +80 -0
  188. data/lib/booth/userland/logins/transitions/new/already_logged_in.rb +21 -0
  189. data/lib/booth/userland/logins/transitions/new/fallible.rb +27 -0
  190. data/lib/booth/userland/logins/transitions/new/mode_first_time.rb +20 -0
  191. data/lib/booth/userland/logins/transitions/new/mode_username_and_password.rb +20 -0
  192. data/lib/booth/userland/logins/transitions/new/mode_username_and_webauth.rb +26 -0
  193. data/lib/booth/userland/logins/transitions/new/mode_username_password_and_otp.rb +24 -0
  194. data/lib/booth/userland/logins/transitions/new/mode_username_password_and_webauth.rb +24 -0
  195. data/lib/booth/userland/logins/transitions/new/no_username_chosen.rb +19 -0
  196. data/lib/booth/userland/logins/transitions/new/remote_session_available.rb +52 -0
  197. data/lib/booth/userland/logins/transitions/new/timed_out.rb +25 -0
  198. data/lib/booth/userland/onboardings/show.rb +74 -0
  199. data/lib/booth/userland/onboardings/transitions/update/choose_mode.rb +58 -0
  200. data/lib/booth/userland/onboardings/transitions/update/choose_password.rb +41 -0
  201. data/lib/booth/userland/onboardings/transitions/update/choose_webauth_nickname.rb +50 -0
  202. data/lib/booth/userland/onboardings/transitions/update/confirm_otp.rb +58 -0
  203. data/lib/booth/userland/onboardings/transitions/update/confirm_password.rb +49 -0
  204. data/lib/booth/userland/onboardings/transitions/update/register_otp.rb +31 -0
  205. data/lib/booth/userland/onboardings/transitions/update/reset_otp.rb +40 -0
  206. data/lib/booth/userland/onboardings/transitions/update/reset_password.rb +35 -0
  207. data/lib/booth/userland/onboardings/transitions/update/reset_webauth.rb +46 -0
  208. data/lib/booth/userland/onboardings/transitions/update/webauth_authentication_initiation.rb +40 -0
  209. data/lib/booth/userland/onboardings/transitions/update/webauth_authentication_verification.rb +59 -0
  210. data/lib/booth/userland/onboardings/transitions/update/webauth_registration_initiation.rb +46 -0
  211. data/lib/booth/userland/onboardings/transitions/update/webauth_registration_verification.rb +56 -0
  212. data/lib/booth/userland/onboardings/update.rb +68 -0
  213. data/lib/booth/userland/otps/destroy.rb +42 -0
  214. data/lib/booth/userland/otps/edit.rb +72 -0
  215. data/lib/booth/userland/otps/guards/manageable.rb +21 -0
  216. data/lib/booth/userland/otps/guards/sudo.rb +23 -0
  217. data/lib/booth/userland/otps/show.rb +36 -0
  218. data/lib/booth/userland/otps/sudo.rb +51 -0
  219. data/lib/booth/userland/otps/transitions/update/confirm.rb +84 -0
  220. data/lib/booth/userland/otps/transitions/update/register.rb +40 -0
  221. data/lib/booth/userland/otps/transitions/update/reset.rb +31 -0
  222. data/lib/booth/userland/otps/update.rb +34 -0
  223. data/lib/booth/userland/password_resets/create.rb +73 -0
  224. data/lib/booth/userland/password_resets/guards/logged_out.rb +21 -0
  225. data/lib/booth/userland/password_resets/new.rb +57 -0
  226. data/lib/booth/userland/password_resets/show.rb +77 -0
  227. data/lib/booth/userland/password_resets/transitions/update/choose_password.rb +48 -0
  228. data/lib/booth/userland/password_resets/transitions/update/confirm_password.rb +54 -0
  229. data/lib/booth/userland/password_resets/transitions/update/reset_password.rb +29 -0
  230. data/lib/booth/userland/password_resets/update.rb +65 -0
  231. data/lib/booth/userland/passwords/destroy.rb +41 -0
  232. data/lib/booth/userland/passwords/edit.rb +54 -0
  233. data/lib/booth/userland/passwords/guards/manageable.rb +21 -0
  234. data/lib/booth/userland/passwords/guards/removable.rb +21 -0
  235. data/lib/booth/userland/passwords/guards/sudo.rb +21 -0
  236. data/lib/booth/userland/passwords/remove.rb +34 -0
  237. data/lib/booth/userland/passwords/show.rb +32 -0
  238. data/lib/booth/userland/passwords/sudo.rb +55 -0
  239. data/lib/booth/userland/passwords/transitions/remove/step.rb +27 -0
  240. data/lib/booth/userland/passwords/transitions/update/choose_password.rb +62 -0
  241. data/lib/booth/userland/passwords/transitions/update/confirm_password.rb +82 -0
  242. data/lib/booth/userland/passwords/update.rb +33 -0
  243. data/lib/booth/userland/personal_contests/show.rb +60 -0
  244. data/lib/booth/userland/personal_contests/update.rb +37 -0
  245. data/lib/booth/userland/recoveries/create.rb +48 -0
  246. data/lib/booth/userland/recoveries/new.rb +35 -0
  247. data/lib/booth/userland/registrations/create.rb +56 -0
  248. data/lib/booth/userland/registrations/new.rb +39 -0
  249. data/lib/booth/userland/sessions/destroy_one_or_other.rb +41 -0
  250. data/lib/booth/userland/sessions/index.rb +27 -0
  251. data/lib/booth/userland/sessions/show.rb +31 -0
  252. data/lib/booth/userland/sessions/transitions/destroy/enter_password.rb +50 -0
  253. data/lib/booth/userland/sessions/transitions/destroy/enter_webauth.rb +56 -0
  254. data/lib/booth/userland/sessions/transitions/destroy/verify_password.rb +83 -0
  255. data/lib/booth/userland/sessions/transitions/destroy/webauth_authentication_initiation.rb +38 -0
  256. data/lib/booth/userland/sessions/transitions/destroy/webauth_authentication_verification.rb +61 -0
  257. data/lib/booth/userland/sessions/transitions/show/enter_webauth.rb +56 -0
  258. data/lib/booth/userland/webauths/create.rb +83 -0
  259. data/lib/booth/userland/webauths/destroy.rb +60 -0
  260. data/lib/booth/userland/webauths/guards/manageable.rb +21 -0
  261. data/lib/booth/userland/webauths/guards/sudo.rb +22 -0
  262. data/lib/booth/userland/webauths/index.rb +43 -0
  263. data/lib/booth/userland/webauths/new.rb +70 -0
  264. data/lib/booth/userland/webauths/sudo.rb +25 -0
  265. data/lib/booth/userland/webauths/transitions/create/authentication_initiation.rb +52 -0
  266. data/lib/booth/userland/webauths/transitions/create/authentication_verification.rb +64 -0
  267. data/lib/booth/userland/webauths/transitions/create/choose_nickname.rb +50 -0
  268. data/lib/booth/userland/webauths/transitions/create/registration_initiation.rb +61 -0
  269. data/lib/booth/userland/webauths/transitions/create/registration_verification.rb +68 -0
  270. data/lib/booth/userland/webauths/transitions/create/reset.rb +36 -0
  271. data/lib/booth/userland/webauths/transitions/new/step.rb +23 -0
  272. data/lib/booth/userland/webauths/transitions/sudo/authentication_initiation.rb +47 -0
  273. data/lib/booth/userland/webauths/transitions/sudo/authentication_verification.rb +34 -0
  274. data/lib/booth/userland.rb +192 -0
  275. data/lib/booth/version.rb +3 -0
  276. data/lib/booth/webauth/authentication_verification.rb +68 -0
  277. data/lib/booth/webauth/demand_user_verification.rb +29 -0
  278. data/lib/booth/webauth/options_for_create.rb +46 -0
  279. data/lib/booth/webauth/options_for_get.rb +29 -0
  280. data/lib/booth.rb +267 -0
  281. data/lib/generators/booth/migration/migration_generator.rb +25 -0
  282. data/lib/generators/booth/migration/templates/add_credential_to_users.erb +18 -0
  283. data/lib/generators/booth/migration/templates/create_booth_mode_types.erb +20 -0
  284. data/lib/generators/booth/migration/templates/create_booth_tables.erb +135 -0
  285. 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