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
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Booth
2
4
  module Concerns
3
- # An "Action" is something that is called from a Rails controller.#
5
+ # An "Action" is something that is called from a Rails controller.
4
6
  # It contains all the logic that the controller action is supposed to execute.
5
7
  # By convention the "authentication scope" and the "request object" are passed in.
6
8
  module Action
@@ -8,10 +10,10 @@ module Booth
8
10
 
9
11
  included do
10
12
  include ::Booth::Logging
11
- include ::Booth::MethodObject
13
+ include Calls
12
14
 
13
- option :scope, ->(scope) { ::Booth::Syntaxes::Scope.call(scope).normalized_scope }
14
- option :request, ::Booth::Request
15
+ option :scope, ::Booth::Coercers::Scope
16
+ option :request, ::Booth::Coercers::Request
15
17
 
16
18
  delegate :params, to: :request, private: true
17
19
  end
@@ -22,36 +24,46 @@ module Booth
22
24
  # May be overriden
23
25
  # ----------------
24
26
 
27
+ # An Action implements at least this method. It holds an Array of Transition classes.
28
+ #
29
+ # If a Transition feels responsible for handling the incoming params,
30
+ # it is instantiated and called, to change the state of the user authentication.
31
+ def transitions
32
+ raise "Implement `#transitions` in #{self}"
33
+ end
34
+
25
35
  def initialize_transition
26
- transition.call(request:)
36
+ transition.call(scope:, request:)
27
37
  end
28
38
 
29
39
  def after_transition; end
30
40
 
31
- def transitions
32
- raise "Implement `#transitions` in #{self}"
33
- end
34
-
35
41
  # ---------------
36
42
  # Never overriden
37
43
  # ---------------
38
44
 
39
45
  # I found this to be a repetitive pattern, so I added this method in this concern.
40
- # It makes the code a little harder to read but probably still more robust.
46
+ # It makes the code a little harder to read but probably more robust.
41
47
  def do_transition
42
48
  if transition
43
- # debug { "Calling Transition #{transition}" }
49
+ message = if ENV['DEBUG']
50
+ "🔹 TRANSITION - #{transition.to_s.demodulize}"
51
+ else
52
+ "Calling Transition #{transition}"
53
+ end
54
+ log { message }
55
+
44
56
  result = initialize_transition
45
57
  after_transition
46
58
  return result
47
59
  end
48
60
 
49
- debug { 'No transition applies to these params' }
61
+ log { "❌ No transition applies to these params: #{params.keys.join(', ')}" }
50
62
  Tron.failure :unknown_transition
51
63
  end
52
64
 
53
65
  def transition
54
- @transition ||= transitions.detect { _1.applicable?(params:) }
66
+ @transition ||= transitions.detect { it.applicable?(params:) }
55
67
  end
56
68
  end
57
69
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Booth
2
4
  module Concerns
3
5
  # A `Booth::Action` usually consists of several `Booth::Transition`s.
@@ -6,9 +8,10 @@ module Booth
6
8
 
7
9
  included do
8
10
  include ::Booth::Logging
9
- include ::Booth::MethodObject
11
+ include Calls
10
12
 
11
- option :request, ::Booth::Request
13
+ option :scope, ::Booth::Coercers::Scope
14
+ option :request, ::Booth::Coercers::Request
12
15
 
13
16
  delegate :params, to: :request, private: true
14
17
  end
@@ -1,31 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Booth
2
4
  # Holds global configuration parameters.
3
5
  class Configuration
4
- def otp_issuer(scope: :default)
5
- @otp_issuers ||= {}
6
+ def initialize
7
+ @logger = rails_logger
8
+ @session_inactivity_lifetime = 1.year
6
9
 
7
- @otp_issuers[scope.to_sym].to_s.presence || 'Login'
10
+ # Need to keep the name `request:` here, because it is checked when calling the lambda.
11
+ @relying_party_resolver = lambda { |request:| # rubocop:disable Lint/UnusedBlockArgument
12
+ raise 'Please set a Booth.config.relying_party_resolver'
13
+ }
8
14
  end
9
15
 
10
- def set_otp_issuer(new_issuer, scope: :default)
11
- @otp_issuers ||= {}
12
-
13
- @otp_issuers[scope.to_sym] = new_issuer
14
- end
15
-
16
- def otp_issuer=(new_issuer)
17
- set_otp_issuer(new_issuer)
18
- end
16
+ attr_accessor :logger, :session_inactivity_lifetime, :relying_party_resolver
19
17
 
20
- def logger
21
- return if @no_logger
22
-
23
- @logger || rails_logger
24
- end
25
-
26
- def logger=(new_logger)
27
- @no_logger = new_logger.nil?
28
- @logger = new_logger
18
+ def interaction_timeout
19
+ 20.minutes
29
20
  end
30
21
 
31
22
  def log_to_rails_and_stdout!
@@ -34,47 +25,9 @@ module Booth
34
25
  ::Booth.config.send(:rails_logger).debug(...)
35
26
  ::Booth.config.send(:stdout_logger).debug(...)
36
27
  end
37
-
38
- def warn(...)
39
- ::Booth.config.send(:rails_logger).warn(...)
40
- ::Booth.config.send(:stdout_logger).warn(...)
41
- end
42
-
43
- def error(...)
44
- ::Booth.config.send(:rails_logger).error(...)
45
- ::Booth.config.send(:stdout_logger).error(...)
46
- end
47
28
  end.new
48
29
  end
49
30
 
50
- def interaction_timeout
51
- 20.minutes
52
- end
53
-
54
- def session_inactivity_lifetime
55
- @session_inactivity_lifetime ||= 3.months
56
- end
57
-
58
- def session_inactivity_lifetime=(new_lifetime)
59
- @session_inactivity_lifetime = new_lifetime
60
- end
61
-
62
- def password_reset_window
63
- 2.hours
64
- end
65
-
66
- def onboarding_window
67
- 1.week
68
- end
69
-
70
- def otp_digits
71
- 6
72
- end
73
-
74
- def contest_digits
75
- 6
76
- end
77
-
78
31
  private
79
32
 
80
33
  # The standard Rails logger does not show `progname`.
@@ -82,19 +35,7 @@ module Booth
82
35
  def rails_logger
83
36
  @rails_logger ||= Class.new do
84
37
  def debug(progname, &block)
85
- ::Rails.logger.debug { "#{ActiveSupport::LogSubscriber.new.send(:color, progname, :blue)} - #{block.call}" }
86
- end
87
-
88
- def info(progname, &block)
89
- ::Rails.logger.info { "#{ActiveSupport::LogSubscriber.new.send(:color, progname, :blue)} - #{block.call}" }
90
- end
91
-
92
- def warn(progname, &block)
93
- ::Rails.logger.warn { "#{ActiveSupport::LogSubscriber.new.send(:color, progname, :blue)} - #{block.call}" }
94
- end
95
-
96
- def error(progname, &block)
97
- ::Rails.logger.error { "#{ActiveSupport::LogSubscriber.new.send(:color, progname, :blue)} - #{block.call}" }
38
+ ::Rails.logger.debug { "#{Paint[progname, :blue]} - #{block.call}" }
98
39
  end
99
40
  end.new
100
41
  end
@@ -109,7 +50,7 @@ module Booth
109
50
 
110
51
  def stdout_logger_formatter
111
52
  proc do |severity, _, progname, message|
112
- [severity.rjust(5), progname, '-', message, "\n"].join(' ')
53
+ "#{severity.rjust(5)} #{Paint[progname, :blue]} - #{message}\n"
113
54
  end
114
55
  end
115
56
  end
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The main module for this Ruby gem.
1
4
  module Booth
2
5
  # Lazy-loads and returns the global configuration instance.
3
6
  #
@@ -24,14 +27,4 @@ module Booth
24
27
  def self.configure
25
28
  yield config
26
29
  end
27
-
28
- # Resets the configuration.
29
- #
30
- # @note This is useful for testing, since the configuration is global
31
- # and persists across tests.
32
- # @api private
33
- #
34
- def self.reset!
35
- @configs = nil
36
- end
37
30
  end
@@ -1,17 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Booth
2
- module Audits
3
- module Register
4
+ module Core
5
+ module Audit
4
6
  class CompletedOnboarding
5
- include ::Booth::MethodObject
7
+ include Calls
6
8
 
7
9
  option :credential
8
10
  option :ip
9
11
  option :agent
10
12
 
11
13
  def call
12
- ::Booth::Models::Audit.create! credential: credential,
13
- ip: ip,
14
- agent: agent,
14
+ ::Booth::Models::Audit.create! credential:,
15
+ ip:,
16
+ agent:,
15
17
  event: :completed_onboarding
16
18
 
17
19
  nil
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Audit
6
+ class CredentialCreated
7
+ include Calls
8
+
9
+ option :credential
10
+ option :ip
11
+ option :agent
12
+
13
+ def call
14
+ ::Booth::Models::Audit.create! credential:,
15
+ ip:,
16
+ agent:,
17
+ event: :registered
18
+
19
+ nil
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Audit
6
+ class Logout
7
+ include Calls
8
+
9
+ option :credential
10
+ option :ip
11
+ option :agent
12
+
13
+ def call
14
+ ::Booth::Models::Audit.create! credential:,
15
+ ip:,
16
+ agent:,
17
+ event: :logout
18
+
19
+ nil
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Authenticators
6
+ class Confirm
7
+ include Calls
8
+ include ::Booth::Logging
9
+
10
+ option :authenticator
11
+ option :sign_count
12
+
13
+ def call
14
+ raise 'Authenticator is already confirmed' if authenticator.confirmed_at
15
+
16
+ log { 'Confirming Authenticator...' }
17
+ authenticator.transaction do
18
+ authenticator.update! sign_count:,
19
+ confirmed_at: Time.current
20
+ end
21
+
22
+ Tron.success :confirmation_successful
23
+ rescue ActiveRecord::ActiveRecordError => e
24
+ error { e }
25
+ Tron.failure :confirmation_failed
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Authenticators
6
+ # Registration status of an Authenticator.
7
+ class Step
8
+ include Calls
9
+
10
+ param :authenticator
11
+
12
+ def call
13
+ return :register if authenticator.device_id.blank? ||
14
+ authenticator.public_key.blank? ||
15
+ authenticator.sign_count.blank?
16
+ return :choose_nickname if authenticator.nickname.blank?
17
+ return :confirm if authenticator.confirmed_at.blank?
18
+
19
+ :completed
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Cooldowns
6
+ class DistanceOfTime
7
+ include Calls
8
+
9
+ option :from
10
+ option :till
11
+
12
+ def call
13
+ result = []
14
+ result.push("#{distance_in_hours} h") if show_hours?
15
+ result.push("#{distance_in_minutes} min") if show_minutes?
16
+ result.push("#{distance_in_seconds} s") if show_seconds?
17
+ result.join(' ')
18
+ end
19
+
20
+ def show_hours?
21
+ distance_in_hours.positive?
22
+ end
23
+
24
+ def show_minutes?
25
+ return false if (((till - from).abs % 3600) / 60) < 1
26
+
27
+ distance_in_minutes.nonzero?
28
+ end
29
+
30
+ def show_seconds?
31
+ return true if distance_in_seconds < 60
32
+
33
+ distance_in_hours.zero? && distance_in_minutes.zero?
34
+ end
35
+
36
+ def distance_in_hours
37
+ ((till - from).abs / 3600).floor
38
+ end
39
+
40
+ def distance_in_minutes
41
+ (((till - from).abs % 3600) / 60).round
42
+ end
43
+
44
+ def distance_in_seconds
45
+ (till - from).abs.round
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Cooldowns
6
+ module Strategies
7
+ class Exponential
8
+ include Calls
9
+ include ::Booth::Logging
10
+
11
+ option :scope
12
+
13
+ def call
14
+ return limit_not_yet_reached! if seconds_to_wait.zero?
15
+
16
+ limit_reached!
17
+ end
18
+
19
+ private
20
+
21
+ def limit_reached!
22
+ log do
23
+ "Wait #{seconds_to_wait}/#{waiting_period} sec for #{number_of_incidents} incidents"
24
+ end
25
+ public_message = I18n.t('booth.try_again_cooldown', distance_of_time_until_cooldown:)
26
+
27
+ ::Booth::Core::Cooldowns::Strategies::Result.failure(
28
+ public_message:,
29
+ attempts_left: 999_999,
30
+ cooldown_at:,
31
+ number_of_incidents:,
32
+ )
33
+ end
34
+
35
+ def limit_not_yet_reached!
36
+ log { 'No need to wait' }
37
+
38
+ ::Booth::Core::Cooldowns::Strategies::Result.success(
39
+ public_message: nil,
40
+ attempts_left: 999_999,
41
+ number_of_incidents:,
42
+ )
43
+ end
44
+
45
+ # Calculation Helpers
46
+
47
+ def cooldown_at
48
+ seconds_to_wait.from_now
49
+ end
50
+
51
+ def seconds_to_wait
52
+ return 0 unless newest_timestamp
53
+
54
+ candidate = newest_timestamp.to_i + waiting_period - Time.current.to_i
55
+ return 0 if candidate.negative?
56
+
57
+ candidate
58
+ end
59
+
60
+ def waiting_period
61
+ return 2.years if number_of_incidents > 9
62
+
63
+ # This effectively implies less than 10 attempts.
64
+ (5**number_of_incidents).seconds
65
+ end
66
+
67
+ # Queries
68
+
69
+ def number_of_incidents
70
+ @number_of_incidents ||= scope.count
71
+ end
72
+
73
+ def newest_timestamp
74
+ return @newest_timestamp if defined? @newest_timestamp
75
+
76
+ @newest_timestamp = scope.maximum(:created_at)
77
+ end
78
+
79
+ # Helpers
80
+
81
+ def distance_of_time_until_cooldown
82
+ ::Booth::Core::Cooldowns::DistanceOfTime.call(from: Time.current, till: cooldown_at)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Cooldowns
6
+ module Strategies
7
+ class Global
8
+ include Calls
9
+ include ::Booth::Logging
10
+
11
+ option :scope
12
+ option :max_attempts
13
+
14
+ def call
15
+ if number_of_incidents >= max_attempts
16
+ limit_reached!
17
+ else
18
+ limit_not_yet_reached!
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def limit_reached!
25
+ log { "Limited globally #{number_of_incidents}/#{max_attempts}" }
26
+
27
+ ::Booth::Core::Cooldowns::Strategies::Result.failure(
28
+ public_message: I18n.t('booth.permanently_blocked'),
29
+ attempts_left:,
30
+ cooldown_at: nil,
31
+ number_of_incidents:,
32
+ )
33
+ end
34
+
35
+ def limit_not_yet_reached!
36
+ log { "Not yet globally limited #{number_of_incidents}/#{max_attempts}" }
37
+
38
+ ::Booth::Core::Cooldowns::Strategies::Result.success(
39
+ public_message:,
40
+ attempts_left:,
41
+ number_of_incidents:,
42
+ )
43
+ end
44
+
45
+ def public_message
46
+ return if number_of_incidents.zero?
47
+
48
+ if attempts_left == 1
49
+ I18n.t 'booth.last_attempt'
50
+ else
51
+ I18n.t 'booth.attempts_left', attempts_left:
52
+ end
53
+ end
54
+
55
+ def attempts_left
56
+ max_attempts - number_of_incidents
57
+ end
58
+
59
+ def number_of_incidents
60
+ @number_of_incidents ||= scope.count
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Cooldowns
6
+ module Strategies
7
+ # All strategies quack the same way.
8
+ # They respond an immutable Tron Data object with additional information.
9
+ module Result
10
+ def self.failure(number_of_incidents:, public_message:, cooldown_at:, attempts_left:)
11
+ Tron.failure :hot, public_message:,
12
+ cooldown_at:,
13
+ attempts_left:,
14
+ number_of_incidents:
15
+ end
16
+
17
+ def self.success(public_message:, number_of_incidents:, attempts_left:)
18
+ Tron.success :cool, number_of_incidents:,
19
+ cooldown_at: nil,
20
+ public_message:,
21
+ attempts_left:
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Credentials
6
+ class Create
7
+ include Calls
8
+ include ::Booth::Logging
9
+
10
+ option :domain
11
+ option :scope
12
+ option :username
13
+
14
+ def call
15
+ if credential.save
16
+ log { "Successfully persisted a new Booth::Models::Credential record with ID #{credential.id.inspect}" }
17
+ Tron.success :credential_created, credential:
18
+ else
19
+ log { 'Could not persist a new Booth::Models::Credential record' }
20
+ Tron.failure :persistence_failed, error: credential.errors.to_a.to_sentence
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def credential
27
+ @credential ||= ::Booth::Models::Credential.new(domain:, username:, scope:)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end