booth 0.0.1 → 0.0.2

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 (383) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/LICENSE.md +1 -2
  4. data/README.md +37 -6
  5. data/app/assets/images/booth/browsers/README.md +1 -2
  6. data/app/assets/images/booth/browsers/chrome.svg +1 -1
  7. data/app/assets/images/booth/browsers/edge.svg +1 -1
  8. data/app/assets/images/booth/browsers/firefox.svg +1 -1
  9. data/app/assets/images/booth/browsers/opera.svg +1 -1
  10. data/app/assets/images/booth/browsers/safari.svg +1 -1
  11. data/app/assets/images/booth/fido/passkey_mark_a.svg +10 -0
  12. data/app/assets/images/booth/fido/passkey_mark_a_black.svg +32 -0
  13. data/app/assets/images/booth/fido/passkey_mark_a_reverse.svg +33 -0
  14. data/app/assets/images/booth/fido/passkey_mark_a_white.svg +32 -0
  15. data/app/assets/images/booth/fido/passkey_mark_b_black.svg +1 -0
  16. data/app/assets/images/booth/platforms/android.svg +1 -6
  17. data/app/assets/images/booth/platforms/apple.svg +1 -6
  18. data/app/assets/images/booth/platforms/linux.svg +1 -6
  19. data/app/assets/images/booth/platforms/windows.svg +1 -6
  20. data/app/assets/javascripts/booth/authentication.js +29 -0
  21. data/app/assets/javascripts/booth/authentication.js.map +1 -0
  22. data/app/assets/javascripts/booth/error.js +38 -0
  23. data/app/assets/javascripts/booth/error.js.map +1 -0
  24. data/app/assets/javascripts/booth/form.js +78 -0
  25. data/app/assets/javascripts/booth/form.js.map +1 -0
  26. data/app/assets/javascripts/booth/gui.js +53 -0
  27. data/app/assets/javascripts/booth/gui.js.map +1 -0
  28. data/app/assets/javascripts/booth/registration.js +29 -0
  29. data/app/assets/javascripts/booth/registration.js.map +1 -0
  30. data/app/assets/javascripts/booth/setup.js +14 -0
  31. data/app/assets/javascripts/booth/verification.js +49 -0
  32. data/app/assets/javascripts/booth/verification.js.map +1 -0
  33. data/app/assets/javascripts/declarations/authentication.d.ts +6 -0
  34. data/app/assets/javascripts/declarations/error.d.ts +36 -0
  35. data/app/assets/javascripts/declarations/form.d.ts +8 -0
  36. data/app/assets/javascripts/declarations/gui.d.ts +4 -0
  37. data/app/assets/javascripts/declarations/registration.d.ts +6 -0
  38. data/app/assets/javascripts/declarations/setup.d.ts +3 -0
  39. data/app/assets/javascripts/declarations/verification.d.ts +6 -0
  40. data/app/assets/javascripts/src/authentication.ts +41 -0
  41. data/app/assets/javascripts/src/error.ts +35 -0
  42. data/app/assets/javascripts/src/form.ts +90 -0
  43. data/app/assets/javascripts/src/gui.ts +59 -0
  44. data/app/assets/javascripts/src/registration.ts +44 -0
  45. data/app/assets/javascripts/src/verification.ts +61 -0
  46. data/app/assets/stylesheets/booth/booth.css +3 -0
  47. data/config/importmap.rb +11 -0
  48. data/config/locales/de.yml +14 -38
  49. data/config/locales/en.yml +17 -36
  50. data/data/combined_aaguid.json +1 -0
  51. data/lib/booth/adminland/credentials/create.rb +10 -12
  52. data/lib/booth/adminland/credentials/index.rb +31 -0
  53. data/lib/booth/adminland/onboardings/create.rb +24 -15
  54. data/lib/booth/adminland/onboardings/destroy.rb +8 -4
  55. data/lib/booth/adminland/onboardings/find.rb +52 -45
  56. data/lib/booth/adminland/onboardings/find_unconsumed.rb +61 -0
  57. data/lib/booth/adminland/onboardings/index.rb +6 -3
  58. data/lib/booth/adminland/periodic_cleanup.rb +7 -2
  59. data/lib/booth/adminland.rb +17 -18
  60. data/lib/booth/coercers/domain.rb +11 -0
  61. data/lib/booth/coercers/request.rb +51 -0
  62. data/lib/booth/coercers/scope.rb +11 -0
  63. data/lib/booth/comparisons/domain.rb +38 -0
  64. data/lib/booth/comparisons/scope.rb +38 -0
  65. data/lib/booth/concerns/action.rb +25 -13
  66. data/lib/booth/concerns/transition.rb +5 -2
  67. data/lib/booth/configuration.rb +14 -73
  68. data/lib/booth/configure.rb +3 -10
  69. data/lib/booth/{audits/register → core/audit}/completed_onboarding.rb +8 -6
  70. data/lib/booth/core/audit/credential_created.rb +24 -0
  71. data/lib/booth/core/audit/logout.rb +24 -0
  72. data/lib/booth/core/authenticators/confirm.rb +30 -0
  73. data/lib/booth/core/authenticators/step.rb +24 -0
  74. data/lib/booth/core/cooldowns/distance_of_time.rb +50 -0
  75. data/lib/booth/core/cooldowns/strategies/exponential.rb +88 -0
  76. data/lib/booth/core/cooldowns/strategies/global.rb +66 -0
  77. data/lib/booth/core/cooldowns/strategies/result.rb +27 -0
  78. data/lib/booth/core/credentials/create.rb +32 -0
  79. data/lib/booth/core/credentials/find_by_username.rb +63 -0
  80. data/lib/booth/core/credentials/index.rb +15 -0
  81. data/lib/booth/core/credentials/webauth_challenge.rb +37 -0
  82. data/lib/booth/core/geolocation.rb +25 -0
  83. data/lib/booth/core/onboardings/find.rb +92 -0
  84. data/lib/booth/core/onboardings/step.rb +19 -0
  85. data/lib/booth/core/remotes/get.rb +45 -0
  86. data/lib/booth/core/remotes/respond.rb +82 -0
  87. data/lib/booth/core/remotes/set_for_login.rb +31 -0
  88. data/lib/booth/core/sessions/create_and_login.rb +63 -0
  89. data/lib/booth/core/sessions/historical_locations.rb +22 -0
  90. data/lib/booth/core/sessions/index.rb +66 -0
  91. data/lib/booth/core/sessions/revoke.rb +59 -0
  92. data/lib/booth/core/sessions/revoke_all_others.rb +49 -0
  93. data/lib/booth/core/sessions/to_passport.rb +35 -0
  94. data/lib/booth/core/webauth/authentication_verification.rb +76 -0
  95. data/lib/booth/core/webauth/options_for_create.rb +56 -0
  96. data/lib/booth/core/webauth/options_for_get.rb +30 -0
  97. data/lib/booth/core/webauth/provider.rb +36 -0
  98. data/lib/booth/core/webauth/registration_verification.rb +100 -0
  99. data/lib/booth/credential.rb +35 -0
  100. data/lib/booth/engine.rb +15 -4
  101. data/lib/booth/errors.rb +2 -0
  102. data/lib/booth/hooks/after_fetch.rb +14 -6
  103. data/lib/booth/hooks/before_logout.rb +5 -3
  104. data/lib/booth/hooks/serialize_from_session.rb +13 -5
  105. data/lib/booth/hooks/serialize_into_session.rb +6 -3
  106. data/lib/booth/logging.rb +13 -42
  107. data/lib/booth/models/application_record.rb +3 -0
  108. data/lib/booth/models/audit.rb +10 -11
  109. data/lib/booth/models/authenticator.rb +6 -9
  110. data/lib/booth/models/credential.rb +17 -20
  111. data/lib/booth/models/onboarding.rb +16 -39
  112. data/lib/booth/models/{contest.rb → remote.rb} +13 -14
  113. data/lib/booth/models/remotes/scopes/recently_created.rb +26 -0
  114. data/lib/booth/models/remotes/scopes/recently_responded.rb +35 -0
  115. data/lib/booth/models/session.rb +15 -10
  116. data/lib/booth/models/user_agent.rb +2 -0
  117. data/lib/booth/request.rb +43 -22
  118. data/lib/booth/requests/agent.rb +3 -1
  119. data/lib/booth/requests/authentication.rb +15 -5
  120. data/lib/booth/requests/ip.rb +4 -2
  121. data/lib/booth/requests/return_path.rb +4 -2
  122. data/lib/booth/requests/session.rb +6 -4
  123. data/lib/booth/requests/storage.rb +5 -31
  124. data/lib/booth/requests/storages/login.rb +35 -29
  125. data/lib/booth/requests/storages/registration.rb +2 -0
  126. data/lib/booth/requests/storages/webauth.rb +3 -0
  127. data/lib/booth/requests/sudo.rb +6 -50
  128. data/lib/booth/routes/userland.rb +13 -59
  129. data/lib/booth/syntaxes/domain.rb +46 -0
  130. data/lib/booth/syntaxes/email.rb +11 -8
  131. data/lib/booth/syntaxes/ip.rb +6 -4
  132. data/lib/booth/syntaxes/remote_code.rb +60 -0
  133. data/lib/booth/syntaxes/scope.rb +7 -3
  134. data/lib/booth/syntaxes/secret_key.rb +8 -6
  135. data/lib/booth/syntaxes/username.rb +23 -10
  136. data/lib/booth/syntaxes/uuid.rb +3 -1
  137. data/lib/booth/test.rb +27 -22
  138. data/lib/booth/testing/incorporation_test_case.rb +29 -0
  139. data/lib/booth/testing/shortcuts.rb +77 -0
  140. data/lib/booth/testing/support/assert_all_partials_were_covered.rb +69 -0
  141. data/lib/booth/testing/support/assert_logged_in.rb +68 -0
  142. data/lib/booth/{test → testing}/support/assert_logged_out.rb +7 -4
  143. data/lib/booth/testing/support/assert_partial.rb +56 -0
  144. data/lib/booth/{test → testing}/support/force_login.rb +10 -4
  145. data/lib/booth/{test → testing}/support/get_session_value.rb +8 -6
  146. data/lib/booth/testing/support/scenario.rb +23 -0
  147. data/lib/booth/testing/support/shortcuts/create_and_onboard.rb +56 -0
  148. data/lib/booth/testing/support/shortcuts/login_with_passkey.rb +55 -0
  149. data/lib/booth/testing/support/shortcuts/register_new_passkey.rb +51 -0
  150. data/lib/booth/testing/support/soft_reset_session.rb +24 -0
  151. data/lib/booth/testing/support/virtual_authenticators/create.rb +34 -0
  152. data/lib/booth/testing/support/virtual_authenticators/destroy.rb +20 -0
  153. data/lib/booth/testing/support/virtual_authenticators/enable.rb +24 -0
  154. data/lib/booth/testing/support/virtual_authenticators/load.rb +38 -0
  155. data/lib/booth/testing/support/virtual_authenticators/manager.rb +124 -0
  156. data/lib/booth/testing/support/visit.rb +62 -0
  157. data/lib/booth/testing/userland/login_remotely.rb +100 -0
  158. data/lib/booth/testing/userland/onboarding_first_time.rb +81 -0
  159. data/lib/booth/testing/userland/onboarding_to_reset_passkeys.rb +129 -0
  160. data/lib/booth/testing/userland/registration_with_passkey.rb +93 -0
  161. data/lib/booth/testing/userland/registration_without_passkey.rb +101 -0
  162. data/lib/booth/testing/userland/sessions_manage_behavior.rb +68 -0
  163. data/lib/booth/testing/userland/sessions_revoke_all_others.rb +17 -0
  164. data/lib/booth/testing/userland/sessions_revoke_one.rb +17 -0
  165. data/lib/booth/testing/userland.rb +36 -0
  166. data/lib/booth/to_struct.rb +9 -2
  167. data/lib/booth/userland/extract_flash_messages.rb +10 -3
  168. data/lib/booth/userland/logins/create.rb +8 -6
  169. data/lib/booth/userland/logins/destroy.rb +23 -6
  170. data/lib/booth/userland/logins/new.rb +23 -25
  171. data/lib/booth/userland/logins/transitions/create/choose_username.rb +62 -27
  172. data/lib/booth/userland/logins/transitions/create/skip_remotes.rb +18 -14
  173. data/lib/booth/userland/logins/transitions/create/webauth_authentication_initiation.rb +54 -48
  174. data/lib/booth/userland/logins/transitions/create/webauth_authentication_verification.rb +62 -58
  175. data/lib/booth/userland/logins/transitions/new/already_logged_in.rb +4 -3
  176. data/lib/booth/userland/logins/transitions/new/fallible.rb +4 -0
  177. data/lib/booth/userland/logins/transitions/new/{mode_username_and_password.rb → missing_authenticators.rb} +5 -4
  178. data/lib/booth/userland/logins/transitions/new/mode_username_and_webauth.rb +6 -4
  179. data/lib/booth/userland/logins/transitions/new/no_username_chosen.rb +3 -1
  180. data/lib/booth/userland/logins/transitions/new/remote_session_available.rb +20 -13
  181. data/lib/booth/userland/logins/transitions/new/timed_out.rb +3 -1
  182. data/lib/booth/userland/onboardings/show.rb +65 -39
  183. data/lib/booth/userland/onboardings/update.rb +46 -38
  184. data/lib/booth/userland/registrations/create.rb +51 -20
  185. data/lib/booth/userland/registrations/new.rb +6 -7
  186. data/lib/booth/userland/remotes/show.rb +56 -0
  187. data/lib/booth/userland/{personal_contests → remotes}/update.rb +5 -3
  188. data/lib/booth/userland/sessions/destroy_one_or_other.rb +3 -16
  189. data/lib/booth/userland/sessions/index.rb +4 -2
  190. data/lib/booth/userland/sessions/show.rb +5 -6
  191. data/lib/booth/userland/sessions/transitions/destroy/enter_webauth.rb +8 -6
  192. data/lib/booth/userland/sessions/transitions/destroy/webauth_authentication_initiation.rb +8 -6
  193. data/lib/booth/userland/sessions/transitions/destroy/webauth_authentication_verification.rb +7 -5
  194. data/lib/booth/userland/sessions/transitions/show/enter_webauth.rb +8 -6
  195. data/lib/booth/userland/webauths/create.rb +20 -17
  196. data/lib/booth/userland/webauths/destroy.rb +6 -16
  197. data/lib/booth/userland/webauths/guards/sudo.rb +10 -5
  198. data/lib/booth/userland/webauths/index.rb +4 -2
  199. data/lib/booth/userland/webauths/new.rb +7 -22
  200. data/lib/booth/userland/webauths/sudo.rb +3 -1
  201. data/lib/booth/userland/webauths/transitions/create/authentication_initiation.rb +8 -11
  202. data/lib/booth/userland/webauths/transitions/create/authentication_verification.rb +11 -13
  203. data/lib/booth/userland/webauths/transitions/create/choose_nickname.rb +8 -5
  204. data/lib/booth/userland/webauths/transitions/create/registration_initiation.rb +15 -14
  205. data/lib/booth/userland/webauths/transitions/create/registration_verification.rb +34 -28
  206. data/lib/booth/userland/webauths/transitions/create/reset.rb +2 -0
  207. data/lib/booth/userland/webauths/transitions/new/step.rb +3 -1
  208. data/lib/booth/userland/webauths/transitions/sudo/authentication_initiation.rb +5 -10
  209. data/lib/booth/userland/webauths/transitions/sudo/authentication_verification.rb +4 -2
  210. data/lib/booth/userland.rb +53 -109
  211. data/lib/booth/version.rb +3 -1
  212. data/lib/booth.rb +6 -236
  213. data/lib/generators/booth/migration/migration_generator.rb +2 -1
  214. data/lib/generators/booth/migration/templates/add_credential_to_users.erb +6 -4
  215. data/lib/generators/booth/migration/templates/create_booth_tables.erb +61 -72
  216. metadata +124 -571
  217. data/app/assets/config/booth_manifest.js +0 -15
  218. data/app/assets/images/booth/browsers/internet_explorer.svg +0 -1
  219. data/app/assets/javascripts/booth/all.js +0 -162
  220. data/app/assets/javascripts/booth/all.js.map +0 -1
  221. data/app/assets/javascripts/booth/booth.ts +0 -194
  222. data/app/assets/javascripts/booth/webauthn-json.ts +0 -99
  223. data/lib/booth/adminland/recoveries/consume.rb +0 -70
  224. data/lib/booth/audits/register/added_otp.rb +0 -22
  225. data/lib/booth/audits/register/changed_otp.rb +0 -22
  226. data/lib/booth/audits/register/correct_otp.rb +0 -42
  227. data/lib/booth/audits/register/correct_password.rb +0 -43
  228. data/lib/booth/audits/register/logout.rb +0 -22
  229. data/lib/booth/audits/register/requested_password_reset.rb +0 -22
  230. data/lib/booth/audits/register/wrong_otp.rb +0 -22
  231. data/lib/booth/audits/register/wrong_password.rb +0 -25
  232. data/lib/booth/authenticators/confirm.rb +0 -34
  233. data/lib/booth/authenticators/credential_mode_after_confirmation.rb +0 -25
  234. data/lib/booth/authenticators/step.rb +0 -19
  235. data/lib/booth/contests/get.rb +0 -36
  236. data/lib/booth/contests/respond.rb +0 -78
  237. data/lib/booth/contests/set_for_login.rb +0 -28
  238. data/lib/booth/cooldowns/distance_of_time.rb +0 -46
  239. data/lib/booth/cooldowns/otp.rb +0 -22
  240. data/lib/booth/cooldowns/password.rb +0 -44
  241. data/lib/booth/cooldowns/password_reset.rb +0 -24
  242. data/lib/booth/cooldowns/strategies/exponential.rb +0 -82
  243. data/lib/booth/cooldowns/strategies/global.rb +0 -62
  244. data/lib/booth/cooldowns/strategies/result.rb +0 -22
  245. data/lib/booth/credentials/create.rb +0 -28
  246. data/lib/booth/credentials/create_with_onboarding.rb +0 -26
  247. data/lib/booth/credentials/find_by_username.rb +0 -45
  248. data/lib/booth/credentials/mode.rb +0 -69
  249. data/lib/booth/credentials/modes/otp_addable.rb +0 -23
  250. data/lib/booth/credentials/modes/otp_changeable.rb +0 -23
  251. data/lib/booth/credentials/modes/otp_manageable.rb +0 -17
  252. data/lib/booth/credentials/modes/otp_removable.rb +0 -23
  253. data/lib/booth/credentials/modes/password_addable.rb +0 -29
  254. data/lib/booth/credentials/modes/password_changeable.rb +0 -31
  255. data/lib/booth/credentials/modes/password_manageable.rb +0 -17
  256. data/lib/booth/credentials/modes/password_removable.rb +0 -24
  257. data/lib/booth/credentials/modes/password_removal_requires_user_verifiable_webauth.rb +0 -16
  258. data/lib/booth/credentials/modes/webauth_addable.rb +0 -26
  259. data/lib/booth/credentials/modes/webauth_manageable.rb +0 -16
  260. data/lib/booth/credentials/modes/webauth_removable.rb +0 -25
  261. data/lib/booth/credentials/otp_authentication.rb +0 -59
  262. data/lib/booth/credentials/password_authentication.rb +0 -72
  263. data/lib/booth/credentials/webauth_challenge.rb +0 -28
  264. data/lib/booth/geolocation.rb +0 -20
  265. data/lib/booth/logger.rb +0 -41
  266. data/lib/booth/method_object.rb +0 -73
  267. data/lib/booth/mode.rb +0 -22
  268. data/lib/booth/models/concerns/modeable.rb +0 -50
  269. data/lib/booth/models/concerns/otpable.rb +0 -37
  270. data/lib/booth/models/concerns/passwordable.rb +0 -58
  271. data/lib/booth/models/contests/scopes/recently_created.rb +0 -23
  272. data/lib/booth/models/contests/scopes/recently_responded.rb +0 -32
  273. data/lib/booth/models/password_reset.rb +0 -41
  274. data/lib/booth/models/recovery.rb +0 -32
  275. data/lib/booth/models/registration.rb +0 -10
  276. data/lib/booth/modes/base.rb +0 -25
  277. data/lib/booth/modes/username_and_password.rb +0 -7
  278. data/lib/booth/modes/username_and_webauth.rb +0 -7
  279. data/lib/booth/modes/username_password_and_otp.rb +0 -7
  280. data/lib/booth/modes/username_password_and_webauth.rb +0 -7
  281. data/lib/booth/onboardings/find.rb +0 -35
  282. data/lib/booth/onboardings/propagate_to_credential.rb +0 -63
  283. data/lib/booth/onboardings/step.rb +0 -68
  284. data/lib/booth/password_resets/create.rb +0 -57
  285. data/lib/booth/password_resets/find.rb +0 -36
  286. data/lib/booth/password_resets/propagate_to_credential.rb +0 -36
  287. data/lib/booth/password_resets/step.rb +0 -18
  288. data/lib/booth/recoveries/create.rb +0 -45
  289. data/lib/booth/requests/storages/otp.rb +0 -54
  290. data/lib/booth/requests/storages/password.rb +0 -49
  291. data/lib/booth/requests/storages/password_reset.rb +0 -35
  292. data/lib/booth/requests/storages/recovery.rb +0 -35
  293. data/lib/booth/sessions/create_and_login.rb +0 -46
  294. data/lib/booth/sessions/historical_locations.rb +0 -18
  295. data/lib/booth/sessions/index.rb +0 -59
  296. data/lib/booth/sessions/revoke.rb +0 -51
  297. data/lib/booth/sessions/revoke_all_others.rb +0 -43
  298. data/lib/booth/sessions/to_passport.rb +0 -51
  299. data/lib/booth/syntaxes/contest_code.rb +0 -58
  300. data/lib/booth/syntaxes/otp.rb +0 -57
  301. data/lib/booth/syntaxes/scope_comparison.rb +0 -28
  302. data/lib/booth/test/helpers.rb +0 -63
  303. data/lib/booth/test/support/assert_all_partials_were_covered.rb +0 -63
  304. data/lib/booth/test/support/assert_logged_in.rb +0 -49
  305. data/lib/booth/test/support/assert_partial.rb +0 -29
  306. data/lib/booth/test/support/otp_code_from_session.rb +0 -30
  307. data/lib/booth/test/support/soft_reset_session.rb +0 -22
  308. data/lib/booth/test/userland/logins/missing_authenticators.rb +0 -72
  309. data/lib/booth/test/userland/logins/missing_onboarding.rb +0 -35
  310. data/lib/booth/test/userland/logins/username_and_password.rb +0 -40
  311. data/lib/booth/test/userland/logins/username_and_webauth.rb +0 -75
  312. data/lib/booth/test/userland/logins/username_password_and_otp.rb +0 -45
  313. data/lib/booth/test/userland/logins/username_password_and_webauth.rb +0 -86
  314. data/lib/booth/test/userland/onboardings/already_logged_in.rb +0 -64
  315. data/lib/booth/test/userland/onboardings/otp.rb +0 -63
  316. data/lib/booth/test/userland/onboardings/password.rb +0 -49
  317. data/lib/booth/test/userland/onboardings/timeout.rb +0 -47
  318. data/lib/booth/test/userland/otps/manage.rb +0 -86
  319. data/lib/booth/test/userland/password_resets/reset.rb +0 -102
  320. data/lib/booth/test/userland.rb +0 -38
  321. data/lib/booth/test/webauthn/disable.rb +0 -17
  322. data/lib/booth/test/webauthn/enable.rb +0 -19
  323. data/lib/booth/test/webauthn/virtual_authenticators/create.rb +0 -38
  324. data/lib/booth/test/webauthn/virtual_authenticators/destroy.rb +0 -20
  325. data/lib/booth/userland/logins/transitions/create/enter_otp.rb +0 -70
  326. data/lib/booth/userland/logins/transitions/create/verify_password.rb +0 -70
  327. data/lib/booth/userland/logins/transitions/new/mode_first_time.rb +0 -20
  328. data/lib/booth/userland/logins/transitions/new/mode_username_password_and_otp.rb +0 -24
  329. data/lib/booth/userland/logins/transitions/new/mode_username_password_and_webauth.rb +0 -24
  330. data/lib/booth/userland/onboardings/transitions/update/choose_mode.rb +0 -58
  331. data/lib/booth/userland/onboardings/transitions/update/choose_password.rb +0 -41
  332. data/lib/booth/userland/onboardings/transitions/update/choose_webauth_nickname.rb +0 -50
  333. data/lib/booth/userland/onboardings/transitions/update/confirm_otp.rb +0 -58
  334. data/lib/booth/userland/onboardings/transitions/update/confirm_password.rb +0 -49
  335. data/lib/booth/userland/onboardings/transitions/update/register_otp.rb +0 -31
  336. data/lib/booth/userland/onboardings/transitions/update/reset_otp.rb +0 -40
  337. data/lib/booth/userland/onboardings/transitions/update/reset_password.rb +0 -35
  338. data/lib/booth/userland/onboardings/transitions/update/reset_webauth.rb +0 -46
  339. data/lib/booth/userland/onboardings/transitions/update/webauth_authentication_initiation.rb +0 -40
  340. data/lib/booth/userland/onboardings/transitions/update/webauth_authentication_verification.rb +0 -59
  341. data/lib/booth/userland/onboardings/transitions/update/webauth_registration_initiation.rb +0 -46
  342. data/lib/booth/userland/onboardings/transitions/update/webauth_registration_verification.rb +0 -56
  343. data/lib/booth/userland/otps/destroy.rb +0 -42
  344. data/lib/booth/userland/otps/edit.rb +0 -72
  345. data/lib/booth/userland/otps/guards/manageable.rb +0 -21
  346. data/lib/booth/userland/otps/guards/sudo.rb +0 -23
  347. data/lib/booth/userland/otps/show.rb +0 -36
  348. data/lib/booth/userland/otps/sudo.rb +0 -51
  349. data/lib/booth/userland/otps/transitions/update/confirm.rb +0 -84
  350. data/lib/booth/userland/otps/transitions/update/register.rb +0 -40
  351. data/lib/booth/userland/otps/transitions/update/reset.rb +0 -31
  352. data/lib/booth/userland/otps/update.rb +0 -34
  353. data/lib/booth/userland/password_resets/create.rb +0 -73
  354. data/lib/booth/userland/password_resets/guards/logged_out.rb +0 -21
  355. data/lib/booth/userland/password_resets/new.rb +0 -57
  356. data/lib/booth/userland/password_resets/show.rb +0 -77
  357. data/lib/booth/userland/password_resets/transitions/update/choose_password.rb +0 -48
  358. data/lib/booth/userland/password_resets/transitions/update/confirm_password.rb +0 -54
  359. data/lib/booth/userland/password_resets/transitions/update/reset_password.rb +0 -29
  360. data/lib/booth/userland/password_resets/update.rb +0 -65
  361. data/lib/booth/userland/passwords/destroy.rb +0 -41
  362. data/lib/booth/userland/passwords/edit.rb +0 -54
  363. data/lib/booth/userland/passwords/guards/manageable.rb +0 -21
  364. data/lib/booth/userland/passwords/guards/removable.rb +0 -21
  365. data/lib/booth/userland/passwords/guards/sudo.rb +0 -21
  366. data/lib/booth/userland/passwords/remove.rb +0 -34
  367. data/lib/booth/userland/passwords/show.rb +0 -32
  368. data/lib/booth/userland/passwords/sudo.rb +0 -55
  369. data/lib/booth/userland/passwords/transitions/remove/step.rb +0 -27
  370. data/lib/booth/userland/passwords/transitions/update/choose_password.rb +0 -62
  371. data/lib/booth/userland/passwords/transitions/update/confirm_password.rb +0 -82
  372. data/lib/booth/userland/passwords/update.rb +0 -33
  373. data/lib/booth/userland/personal_contests/show.rb +0 -60
  374. data/lib/booth/userland/recoveries/create.rb +0 -48
  375. data/lib/booth/userland/recoveries/new.rb +0 -35
  376. data/lib/booth/userland/sessions/transitions/destroy/enter_password.rb +0 -50
  377. data/lib/booth/userland/sessions/transitions/destroy/verify_password.rb +0 -83
  378. data/lib/booth/userland/webauths/guards/manageable.rb +0 -21
  379. data/lib/booth/webauth/authentication_verification.rb +0 -68
  380. data/lib/booth/webauth/demand_user_verification.rb +0 -29
  381. data/lib/booth/webauth/options_for_create.rb +0 -46
  382. data/lib/booth/webauth/options_for_get.rb +0 -29
  383. data/lib/generators/booth/migration/templates/create_booth_mode_types.erb +0 -20
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Credentials
6
+ # Returns Tron Data
7
+ #
8
+ # Success and Failure always include:
9
+ # normalized_username
10
+ # normalized_invalid_username
11
+ #
12
+ # Success additionally includes:
13
+ # credential
14
+ #
15
+ # Failure additionally includes:
16
+ # public_message
17
+ #
18
+ class FindByUsername
19
+ include ::Booth::Logging
20
+ include Calls
21
+
22
+ option :username
23
+
24
+ def call
25
+ do_check_username_syntax
26
+ .on_success { do_find_credential }
27
+ end
28
+
29
+ private
30
+
31
+ attr_accessor :normalized_username, :normalized_invalid_username
32
+
33
+ def do_check_username_syntax
34
+ checking = ::Booth::Syntaxes::Username.call(username)
35
+ self.normalized_username = checking.normalized_username
36
+ self.normalized_invalid_username = checking.normalized_invalid_username
37
+
38
+ checking
39
+ end
40
+
41
+ def do_find_credential
42
+ if credential
43
+ Tron.success(:found_existing_credential, credential:,
44
+ normalized_username:,
45
+ normalized_invalid_username:)
46
+ else
47
+ Tron.failure :credential_not_found, normalized_username:,
48
+ normalized_invalid_username:,
49
+ public_message: I18n.t('booth.unknown_username')
50
+ end
51
+ end
52
+
53
+ # Helpers
54
+
55
+ def credential
56
+ return @credential if defined?(@credential)
57
+
58
+ @credential = ::Booth::Models::Credential.find_by(username: normalized_username)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Credentials
6
+ class Index
7
+ include Calls
8
+
9
+ option :domain
10
+
11
+ def call; end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Credentials
6
+ # See https://github.com/cedarcode/webauthn-rails-demo-app/blob/d9b73e20a7272e8d9f7a26c48ec49e5100295939/app/controllers/sessions_controller.rb#L7-L23
7
+ class WebauthChallenge
8
+ include ::Booth::Logging
9
+ include Calls
10
+
11
+ option :credential
12
+ option :request
13
+
14
+ def call
15
+ return Tron.failure :missing_credential, challenge: nil if credential.nil?
16
+
17
+ if credential.registered_authenticator_ids.blank?
18
+ return Tron.failure :no_known_authenticators,
19
+ challenge: nil
20
+ end
21
+
22
+ Tron.success :here_is_your_challenge, options_for_get: options_for_get.to_json,
23
+ challenge: options_for_get.challenge
24
+ end
25
+
26
+ private
27
+
28
+ def options_for_get
29
+ @options_for_get ||= ::Booth::Core::Webauth::OptionsForGet.call(
30
+ allowed_device_ids: credential.registered_authenticator_ids,
31
+ request:,
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Geolocation
6
+ def self.lookup(ip)
7
+ return 'localhost' if ['127.0.0.1', '::1'].include?(ip.to_s)
8
+ return unless defined?(::DbipUtil)
9
+
10
+ record = ::DbipUtil::City.get(ip.to_s)
11
+ return unless record
12
+
13
+ record['city']['names']['de'] ||
14
+ record['city']['names']['en'] ||
15
+ record['subdivisions']['names']['de'] ||
16
+ record['subdivisions']['names']['en'] ||
17
+ record['country']['names']['de'] ||
18
+ record['country']['names']['en']
19
+
20
+ rescue IPAddr::InvalidAddressError
21
+ nil
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Onboardings
6
+ class Find
7
+ include ::Booth::Logging
8
+ include Calls
9
+
10
+ option :domain
11
+ option :scope
12
+ option :secret_key
13
+ option :consumed
14
+
15
+ def call
16
+ do_check_domain_syntax
17
+ .on_success { do_check_scope_syntax }
18
+ .on_success { do_check_secret_key_syntax }
19
+ .on_success { do_find_onboarding }
20
+ .on_success { do_compare_domain }
21
+ .on_success { do_compare_scope }
22
+ .on_success { do_check_consummation }
23
+ .on_success { do_check_blockage }
24
+ .on_success { do_check_timeout }
25
+ .on_success { do_return_result }
26
+ end
27
+
28
+ private
29
+
30
+ attr_accessor :onboarding
31
+
32
+ def do_check_domain_syntax
33
+ ::Booth::Syntaxes::Domain.call(domain)
34
+ end
35
+
36
+ def do_check_scope_syntax
37
+ ::Booth::Syntaxes::Scope.call(scope)
38
+ end
39
+
40
+ def do_check_secret_key_syntax
41
+ ::Booth::Syntaxes::SecretKey.call(secret_key)
42
+ end
43
+
44
+ def do_find_onboarding
45
+ self.onboarding = ::Booth::Models::Onboarding.joins(:credential).find_by(secret_key:)
46
+
47
+ if onboarding
48
+ log { "Found Onboarding with ID #{onboarding.id.inspect} for secret key #{secret_key}" }
49
+ return Tron.success(:found_onboarding_for_key)
50
+ end
51
+
52
+ log { "Could not find Onboarding with secret key #{secret_key.inspect}" }
53
+ Tron.failure :onboarding_not_found, public_message: I18n.t('booth.unknown_secret_key')
54
+ end
55
+
56
+ def do_compare_domain
57
+ ::Booth::Comparisons::Domain.call(actual: domain, expected: onboarding.domain)
58
+ end
59
+
60
+ def do_compare_scope
61
+ ::Booth::Comparisons::Scope.call(actual: onboarding.scope, expected: scope)
62
+ end
63
+
64
+ def do_check_consummation
65
+ return Tron.success :onboarding_consummation_ignored if consumed == :any
66
+ return Tron.success :onboarding_matches_consumed if consumed && onboarding.consumed?
67
+ return Tron.success :onboarding_matches_unconsumed if !consumed && !onboarding.consumed?
68
+
69
+ Tron.failure(:onboarding_consummation_mismatch, expected: consumed,
70
+ actual: onboarding.consumed?)
71
+ end
72
+
73
+ def do_check_blockage
74
+ return Tron.success(:credential_not_blocked) unless onboarding.credential.blocked?
75
+
76
+ Tron.failure :credential_blocked, public_message: I18n.t('booth.administratively_blocked')
77
+ end
78
+
79
+ def do_check_timeout
80
+ return Tron.success(:onboarding_recently_created) unless onboarding.timed_out?
81
+
82
+ public_message = I18n.t('booth.onboarding_timeout', lifespan_hours: onboarding.lifespan.seconds / 60 / 60)
83
+ Tron.failure :onboarding_outdated, public_message:
84
+ end
85
+
86
+ def do_return_result
87
+ Tron.success(:found_onboarding, onboarding:)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Onboardings
6
+ class Step
7
+ include Calls
8
+
9
+ option :onboarding
10
+ option :request
11
+
12
+ def call
13
+ return :not_found unless onboarding
14
+
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Remotes
6
+ # Retrieves the current Remote for a Credential.
7
+ class Get
8
+ include Calls
9
+ include ::Booth::Logging
10
+
11
+ option :credential_id
12
+
13
+ def call
14
+ return Tron.failure :remote_not_found unless remote
15
+
16
+ Tron.success(:found_recent_remote, **attributes)
17
+ end
18
+
19
+ private
20
+
21
+ def remote
22
+ return @remote if defined?(@remote)
23
+
24
+ @remote = ::Booth::Models::Remote.recently_created_scope
25
+ .find_by(credential_id:)
26
+ end
27
+
28
+ def attributes # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
29
+ {
30
+ formatted_code: remote.formatted_code,
31
+ normalized_code: remote.code,
32
+ ip: remote.ip,
33
+ agent: remote.agent.presence,
34
+ location: remote.location.presence,
35
+ recently_responded: remote.recently_responded?,
36
+ browser_name: remote.browser_name,
37
+ platform_name: remote.platform_name,
38
+ browser_image_path: remote.browser_image_path,
39
+ platform_image_path: remote.platform_image_path
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Remotes
6
+ class Respond
7
+ include ::Booth::Logging
8
+ include Calls
9
+
10
+ option :scope
11
+ option :remote
12
+ option :request
13
+
14
+ def call
15
+ do_find_remote
16
+ .on_success { do_check_timeout }
17
+ .on_success { do_check_scope }
18
+ .on_success { do_check_already_responded }
19
+ .on_success { do_check_code_syntax }
20
+ .on_success { do_respond }
21
+ end
22
+
23
+ private
24
+
25
+ delegate :credential, to: :remote, private: true
26
+
27
+ def do_find_remote
28
+ return Tron.success :remote_exists if remote
29
+
30
+ Tron.failure :missing_remote
31
+ end
32
+
33
+ def do_check_timeout
34
+ return Tron.success :remoteed_recently if remote.recently_created?
35
+
36
+ log { 'This remote timed out' }
37
+ Tron.failure :remote_timed_out,
38
+ lifespan: remote.lifespan,
39
+ public_message: I18n.t('booth.remote_timed_out',
40
+ lifespan_minutes: remote.lifespan.seconds / 60)
41
+ end
42
+
43
+ def do_check_scope
44
+ ::Booth::Comparisons::Scope.call expected: scope, actual: credential.scope
45
+ end
46
+
47
+ def do_check_already_responded
48
+ return Tron.success :ok_waiting_for_response if remote.responded_at.blank?
49
+
50
+ log { "This remote has already been responded to #{remote.responded_at.inspect}" }
51
+ Tron.failure :already_responded,
52
+ public_message: I18n.t('booth.already_responded_to_remote')
53
+ end
54
+
55
+ def do_check_code_syntax
56
+ check = ::Booth::Syntaxes::RemoteCode.call(code_param)
57
+
58
+ check.on_success do
59
+ @normalized_code = check.normalized_remote_code
60
+ end
61
+
62
+ check
63
+ end
64
+
65
+ def do_respond
66
+ if @normalized_code == remote.code
67
+ log { "The code #{@normalized_code} was accepted, persisting positive response..." }
68
+ remote.update!(responded_at: Time.current)
69
+ return Tron.success :response_code_accepted,
70
+ public_message: I18n.t('booth.remote_response_accepted')
71
+ end
72
+
73
+ Tron.failure :wrong_code, public_message: I18n.t('booth.wrong_response_code')
74
+ end
75
+
76
+ def code_param
77
+ request.params.expect(remote_login: [:code])[:code]
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Remotes
6
+ class SetForLogin
7
+ include ::Booth::Logging
8
+ include Calls
9
+
10
+ option :credential_id
11
+ option :request
12
+
13
+ def call
14
+ remote = nil
15
+
16
+ ::Booth::Models::ApplicationRecord.transaction do
17
+ ::Booth::Models::Remote.where(credential_id:).delete_all
18
+
19
+ remote = ::Booth::Models::Remote.create!(
20
+ credential_id:,
21
+ ip: request.ip,
22
+ agent: request.agent,
23
+ )
24
+ end
25
+
26
+ Tron.success :remote_created, remote:
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Sessions
6
+ class CreateAndLogin
7
+ include Calls
8
+ include ::Booth::Logging
9
+
10
+ option :domain
11
+ option :scope
12
+ option :credential
13
+ option :request, ::Booth::Coercers::Request
14
+
15
+ def call
16
+ do_compare_domain
17
+ .on_success { do_compare_scope }
18
+ .on_success { do_create_session }
19
+ .on_success { do_login }
20
+ end
21
+
22
+ private
23
+
24
+ attr_accessor :session
25
+
26
+ def do_compare_domain
27
+ ::Booth::Comparisons::Domain.call(actual: domain, expected: credential.domain)
28
+ end
29
+
30
+ def do_compare_scope
31
+ ::Booth::Comparisons::Scope.call(actual: scope, expected: credential.scope)
32
+ end
33
+
34
+ def do_create_session
35
+ log { "Creating new Session for credential ID #{credential.id}" }
36
+ self.session = ::Booth::Models::Session.create! credential:,
37
+ agent: request.agent,
38
+ most_recent_ip: request.ip
39
+ Tron.success :session_created
40
+ end
41
+
42
+ def do_login
43
+ request.authentication.login(session:)
44
+
45
+ # Consume everything that led up to this authentication to avoid re-authentication.
46
+ # If we don't reset the cookie here, the "remote login flow" could log you right back in.
47
+ login_storage.reset
48
+ registration_storage.reset
49
+
50
+ Tron.success :session_created_and_logged_in, return_path: request.return_path
51
+ end
52
+
53
+ def login_storage
54
+ request.storage.login
55
+ end
56
+
57
+ def registration_storage
58
+ request.storage.registration
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Sessions
6
+ class HistoricalLocations
7
+ include Calls
8
+
9
+ param :session
10
+
11
+ # TODO: Show IPs for cities that are different from the most recent IP.
12
+ def call
13
+ return if historical_locations.values.blank?
14
+
15
+ historical_locations.values.reject { it == location }.uniq.sort.join(', ').presence
16
+ end
17
+
18
+ delegate :location, :historical_locations, to: :session, private: true
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Sessions
6
+ class Index
7
+ EXPOSED_ATTRIBUTE_NAMES = %i[
8
+ id
9
+ activity_at
10
+ created_at
11
+ agent
12
+ most_recent_ip
13
+ historical_ips
14
+ location
15
+ historical_location_names
16
+ browser_name
17
+ platform_name
18
+ browser_image_path
19
+ platform_image_path
20
+ ].freeze
21
+ private_constant :EXPOSED_ATTRIBUTE_NAMES
22
+
23
+ include Calls
24
+
25
+ option :credential_id
26
+ option :current_session_id
27
+
28
+ def call
29
+ # Booth currently doesn't perform pagination.
30
+ # To avoid an attacking vector that could bring our database down,
31
+ # we simply pull the handbrake when trying to fetch too many sessions.
32
+ # Nobody should have that many active concurrent sessions.
33
+ if base_scope.count > max_allowed_sessions
34
+ raise "Too many sessions for Credential ID #{credential_id}"
35
+ end
36
+
37
+ base_scope.map do |session|
38
+ ::Booth::ToStruct.call(exposed_attributes(session))
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def exposed_attributes(session)
45
+ result = { is_current: current_session_id == session.id }
46
+
47
+ EXPOSED_ATTRIBUTE_NAMES.each do |attribute|
48
+ result[attribute] = session.public_send(attribute)
49
+ end
50
+
51
+ result
52
+ end
53
+
54
+ def base_scope
55
+ ::Booth::Models::Session.active_scope
56
+ .sorted_scope
57
+ .owned_by_scope(credential_id:)
58
+ end
59
+
60
+ def max_allowed_sessions
61
+ 500
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Sessions
6
+ class Revoke
7
+ include Calls
8
+ include ::Booth::Logging
9
+
10
+ option :credential_id
11
+ option :session_id
12
+
13
+ def call
14
+ do_check_id_syntax
15
+ .on_success { do_revoke_session }
16
+ end
17
+
18
+ private
19
+
20
+ def do_check_id_syntax
21
+ ::Booth::Syntaxes::Uuid.call(credential_id, raise_if_invalid: true)
22
+ ::Booth::Syntaxes::Uuid.call(session_id, raise_if_invalid: true)
23
+
24
+ Tron.success :valid_session_id_syntax
25
+ end
26
+
27
+ def do_revoke_session
28
+ unless session
29
+ log do
30
+ "Session #{session_id.inspect} not found for credential #{credential_id.inspect}"
31
+ end
32
+ return Tron.success :session_not_found
33
+ end
34
+
35
+ if session.revoked_at.present?
36
+ log { "Session #{session.id.inspect} is already revoked." }
37
+ return Tron.success :already_revoked,
38
+ public_message: I18n.t('booth.session_revoked',
39
+ ip: session.most_recent_ip)
40
+ end
41
+
42
+ log { "Revoking Session #{session.id.inspect}" }
43
+ session.update! revoked_at: Time.current,
44
+ revoke_reason: :manual
45
+
46
+ Tron.success :session_revoked,
47
+ public_message: I18n.t('booth.session_revoked', ip: session.most_recent_ip)
48
+ end
49
+
50
+ def session
51
+ return @session if defined?(@session)
52
+
53
+ @session = ::Booth::Models::Session.where(credential_id:)
54
+ .find_by(id: session_id)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Sessions
6
+ class RevokeAllOthers
7
+ include Calls
8
+
9
+ option :credential_id
10
+ option :surviving_session_id
11
+
12
+ def call
13
+ do_check_id_syntax
14
+ .on_success { do_revoke_all_other_session }
15
+ end
16
+
17
+ private
18
+
19
+ def do_check_id_syntax
20
+ ::Booth::Syntaxes::Uuid.call(credential_id, raise_if_invalid: true)
21
+ ::Booth::Syntaxes::Uuid.call(surviving_session_id, raise_if_invalid: true)
22
+
23
+ Tron.success :valid_session_id_syntax
24
+ end
25
+
26
+ def do_revoke_all_other_session
27
+ if sessions.blank?
28
+ return Tron.success :nothing_to_revoke,
29
+ public_message: I18n.t('booth.all_other_sessions_revoked')
30
+ end
31
+
32
+ # Preferring speed. Because if there are many we don't want the page to hang.
33
+ sessions.update_all revoked_at: Time.current,
34
+ revoke_reason: :manual_all_others
35
+
36
+ Tron.success :other_sessions_revoked,
37
+ public_message: I18n.t('booth.all_other_sessions_revoked')
38
+ end
39
+
40
+ def sessions
41
+ return @sessions if defined?(@sessions)
42
+
43
+ @sessions = ::Booth::Models::Session.where(credential_id:)
44
+ .where.not(id: surviving_session_id)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end