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,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Testing
5
+ module Support
6
+ module Shortcuts
7
+ class LoginWithPasskey
8
+ include ::Calls
9
+ include ::Capybara::DSL
10
+ include ::Booth::Logging
11
+
12
+ option :routing_namespace
13
+ option :scope
14
+ option :username
15
+
16
+ def call
17
+ log { 'Initiating Test Shortcut for logging in with passkey' }
18
+ ::Booth::Testing::Support::Visit.call(routing_namespace:,
19
+ controller: :logins,
20
+ action: :new)
21
+
22
+ ::Booth::Testing::Support::AssertPartial.call(namespace: :userland,
23
+ controller: :logins,
24
+ step: :enter_username)
25
+
26
+ fill_in :username, with: username
27
+ click_on :submit
28
+
29
+ # Wait for HTML to be loaded before checking its <template>
30
+ assert_selector 'template', visible: false
31
+
32
+ if page.html.include?('userland/logins/remote_session_available')
33
+ ::Booth::Testing::Support::AssertPartial.call(namespace: :userland,
34
+ controller: :logins,
35
+ step: :remote_session_available)
36
+ click_on :skip
37
+ end
38
+
39
+ ::Booth::Testing::Support::AssertPartial.call(namespace: :userland,
40
+ controller: :logins,
41
+ step: :enter_webauth)
42
+
43
+ click_on :authenticate
44
+
45
+ AssertLoggedIn.call(scope:, username:)
46
+
47
+ log { 'Shortcut to login with passkey succeeded' }
48
+
49
+ nil
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Testing
5
+ module Support
6
+ module Shortcuts
7
+ class RegisterNewPasskey
8
+ include ::Calls
9
+ include ::Capybara::DSL
10
+
11
+ option :routing_namespace
12
+ option :scope
13
+ option :username
14
+
15
+ def call
16
+ ::Booth::Testing::Support::Visit.call(routing_namespace:,
17
+ controller: :webauths,
18
+ action: :new)
19
+
20
+ ::Booth::Testing::Support::AssertPartial.call(namespace: :userland,
21
+ controller: :webauths,
22
+ step: :register)
23
+
24
+ click_on :register
25
+
26
+ ::Booth::Testing::Support::AssertPartial.call(namespace: :userland,
27
+ controller: :webauths,
28
+ step: :choose_nickname)
29
+
30
+ fill_in :nickname, with: 'Latchkey'
31
+ click_on :submit
32
+
33
+ ::Booth::Testing::Support::AssertPartial.call(namespace: :userland,
34
+ controller: :webauths,
35
+ step: :confirm)
36
+
37
+ click_on :test
38
+
39
+ ::Booth::Testing::Support::AssertPartial.call(namespace: :userland,
40
+ controller: :webauths,
41
+ step: :completed)
42
+
43
+ AssertLoggedIn.call(scope:, username:)
44
+
45
+ nil
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Testing
5
+ module Support
6
+ # Essentially resets the session cookie, but without removing
7
+ # Chrome's Virtual Authenticator Enviroment. Like a force logout.
8
+ class SoftResetSession
9
+ include ::Calls
10
+ include ::Capybara::DSL
11
+ include ::Booth::Logging
12
+
13
+ def call
14
+ ::Capybara::Lockstep.synchronize
15
+
16
+ keys = page.get_rack_session.keys
17
+ keys_with_nil_values = keys.index_with { nil }
18
+
19
+ page.set_rack_session(**keys_with_nil_values)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Testing
5
+ module Support
6
+ module VirtualAuthenticators
7
+ class Create
8
+ include ::Calls
9
+ include ::Capybara::DSL
10
+ include ::Booth::Logging
11
+
12
+ option :has_user_verification
13
+
14
+ def call
15
+ log { "Adding Virtual Authenticator... #{options.as_json}" }
16
+ page.driver.browser.add_virtual_authenticator(options)
17
+ end
18
+
19
+ private
20
+
21
+ # See `bundle open selenium-webdriver`
22
+ # See https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/#type-VirtualAuthenticatorOptions
23
+ def options
24
+ ::Selenium::WebDriver::VirtualAuthenticatorOptions.new.tap do |instance|
25
+ instance.user_verification = has_user_verification
26
+ instance.user_verified = true
27
+ # instance.attestation = 'direct' # or 'indirect'
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Testing
5
+ module VirtualAuthenticators
6
+ class Destroy
7
+ include ::Booth::Logging
8
+ include Calls
9
+
10
+ option :devtools
11
+ option :id
12
+
13
+ def call
14
+ log { "Removing Virtual Authenticator with ID #{id}" }
15
+ devtools.send_cmd 'WebAuthn.removeVirtualAuthenticator', authenticatorId: id
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Testing
5
+ module Support
6
+ module VirtualAuthenticators
7
+ # Start the Virtual Authenticator Environment in Chrome.
8
+ class Enable
9
+ include ::Calls
10
+ include ::Capybara::DSL
11
+ include ::Booth::Logging
12
+
13
+ def call
14
+ log { 'Ensuring enabled Chrome Virtual Authenticator Environment...' }
15
+ # The Environment *randomly* leaks from test to test, disabling it first works.
16
+ # All you'll see is `NotAllowedError` in the JS console if you miss this.
17
+ page.driver.browser.devtools.send_cmd 'WebAuthn.disable'
18
+ page.driver.browser.devtools.send_cmd 'WebAuthn.enable'
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Testing
5
+ module Support
6
+ module VirtualAuthenticators
7
+ class Load
8
+ include ::Calls
9
+ include ::Capybara::DSL
10
+ include ::Booth::Logging
11
+
12
+ option :virtual_authenticator
13
+
14
+ def call
15
+ credential = virtual_authenticator.credentials.first
16
+
17
+ # p 'A' * 100
18
+ # pp credential.instance_variable_get(:@sign_count)
19
+ # p Booth::Models::Authenticator.sole.sign_count
20
+ # p 'B' * 100
21
+
22
+ instance = ::Booth::Testing::Support::VirtualAuthenticators::Create.call(
23
+ has_user_verification: true,
24
+ )
25
+
26
+ log do
27
+ "Attaching Virtual Authenticator Secrets to #{virtual_authenticator.instance_variable_get(:@id)}"
28
+ end
29
+ instance.add_credential(credential)
30
+ # virtual_authenticator.instance_variable_set(:@id, instance.instance_variable_get(:@sign_count))
31
+
32
+ instance
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Testing
5
+ module Support
6
+ module VirtualAuthenticators
7
+ # Capybara handles multiple separate Chrome sessions (like separate browsers).
8
+ # In Chrome each of those sessions is completely distinct and nows nothing about the others.
9
+ # To "simulate" having the *same* passkey in "two browsers", we need to clone and sync it.
10
+ # This means transferring the secret key, and synchronizing the sign count.
11
+ # This class holds a state of all browser sessions to easier facilitate transfer and sync.
12
+ class Manager
13
+ include ::Capybara::DSL
14
+ include ::Booth::Logging
15
+
16
+ # Creates a brand new passkey.
17
+ def create(has_user_verification: true)
18
+ ::Booth::Testing::Support::VirtualAuthenticators::Enable.call
19
+
20
+ new_device = ::Booth::Testing::Support::VirtualAuthenticators::Create.call(
21
+ has_user_verification:,
22
+ )
23
+
24
+ this_session[new_device.instance_variable_get(:@id)] = new_device
25
+
26
+ nil
27
+ end
28
+
29
+ # Takes a passkey from (the) other browser and injects it into this browser.
30
+ def clone_from_other_session
31
+ new_device = ::Booth::Testing::Support::VirtualAuthenticators::Create.call(
32
+ has_user_verification: true,
33
+ )
34
+ new_device.add_credential(other_credential)
35
+
36
+ this_session[new_device.instance_variable_get(:@id)] = new_device
37
+ nil
38
+ end
39
+
40
+ # Inspects a passkey in (the) other browser and syncs the passkey here if needed.
41
+ def refresh_from_other_session
42
+ this_sign_count = this_credential.instance_variable_get(:@sign_count)
43
+ other_sign_count = other_credential.instance_variable_get(:@sign_count)
44
+
45
+ if this_sign_count == other_sign_count
46
+ log { "Sign count of both Virtual Keys is #{this_sign_count}" }
47
+ return
48
+ end
49
+
50
+ log { "Changing Virtual Key sign count from #{this_sign_count} to #{other_sign_count}" }
51
+
52
+ new_credential = this_credential
53
+ # This only affects the Credential here in our Ruby instance.
54
+ new_credential.instance_variable_set(:@sign_count, other_sign_count)
55
+
56
+ # So let us send our Ruby instance to the browser by replacing the key there.
57
+ this_device.remove_all_credentials
58
+ this_device.add_credential(new_credential)
59
+
60
+ nil
61
+ end
62
+
63
+ private
64
+
65
+ def this_credential
66
+ these_credentials = this_device.credentials
67
+
68
+ unless these_credentials.size == 1
69
+ raise "Don't know which other Virtual Credential to pick: #{these_credentials}"
70
+ end
71
+
72
+ these_credentials.first
73
+ end
74
+
75
+ def other_credential
76
+ other_credentials = other_device.credentials
77
+
78
+ unless other_credentials.size == 1
79
+ raise "Don't know which Virtual Credential of mine to pick: #{other_credentials}"
80
+ end
81
+
82
+ other_credentials.first
83
+ end
84
+
85
+ def this_device
86
+ unless this_session.size == 1
87
+ raise "Don't know which Virtual Authenticator of mine to pick: #{this_session.keys}"
88
+ end
89
+
90
+ this_session.values.first
91
+ end
92
+
93
+ def other_device
94
+ unless other_session.size == 1
95
+ raise "Don't know which other Virtual Authenticator to pick: #{other_session.keys}"
96
+ end
97
+
98
+ other_session.values.first
99
+ end
100
+
101
+ def this_session
102
+ sessions[Capybara.session_name]
103
+ end
104
+
105
+ def other_session
106
+ other_session_keys = sessions.keys.reject { it == Capybara.session_name }
107
+
108
+ unless other_session_keys.size == 1
109
+ raise "Cannot determine #{sessions.keys} as alternative to #{Capybara.session_name}"
110
+ end
111
+
112
+ sessions[other_session_keys.first]
113
+ end
114
+
115
+ def sessions
116
+ @sessions ||= {}
117
+ @sessions[Capybara.session_name] ||= {}
118
+ @sessions
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Testing
5
+ module Support
6
+ class Visit
7
+ include ::Calls
8
+ include ::Capybara::DSL
9
+ include ::Booth::Logging
10
+
11
+ option :routing_namespace # :community
12
+ option :controller # :registrations
13
+ option :action # :new
14
+ option :params, default: -> { {} } # { id: 42 }
15
+
16
+ def call
17
+ log { "-----> Visiting #{controller}/#{action} #{params.presence} on #{subdomain}.localhost" }
18
+ visit ::Rails.application.routes.url_helpers.url_for(options)
19
+ end
20
+
21
+ private
22
+
23
+ # The `url_for` helper dissects options into three categories:
24
+ #
25
+ # Path Params
26
+ # - :controller
27
+ # - :action
28
+ # - :anchor
29
+ #
30
+ # URL Params
31
+ # - :protocol
32
+ # - :port
33
+ # - :domain
34
+ # - :subdomain
35
+ # - :host (shortcut for :domain + :subdomain)
36
+ # - :only_path
37
+ #
38
+ # Query Params (everything not absorbed by the other to categories)
39
+ #
40
+ def options
41
+ params.merge subdomain:,
42
+ domain: 'localhost',
43
+ controller: namespaced_controller,
44
+ action:
45
+ end
46
+
47
+ def subdomain
48
+ uri = URI.parse(Capybara.app_host) # 'http://one.two.localhost'
49
+ host_parts = uri.host.to_s.split('.') # ['one', 'two']
50
+ host_parts[0...-1].join('.') # 'one.two'
51
+ end
52
+
53
+ def namespaced_controller
54
+ raise "Controller names must be plural (#{controller})" unless controller.end_with?('s')
55
+ return controller if routing_namespace.blank?
56
+
57
+ "#{routing_namespace}/#{controller}"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Testing
5
+ module Userland
6
+ class LoginRemotely < ::Booth::Testing::IncorporationTestCase
7
+ def call
8
+ before_test&.call
9
+
10
+ create_and_onboard(username: 'alice')
11
+ virtual_authenticators.create
12
+ register_new_passkey(username: 'alice')
13
+
14
+ visit_namespaced controller: :remote_logins, action: :show
15
+
16
+ # ---------------- SIGNIFICANT TEST ------------------
17
+ # Can only enter a code when there is a pending Remote
18
+ # ----------------------------------------------------
19
+ assert_userland_view controller: :remote_logins, step: :no_remote
20
+
21
+ code = nil
22
+ using_session(:other_device) do
23
+ # Login on other device
24
+
25
+ visit_namespaced controller: :logins, action: :new
26
+
27
+ assert_userland_view controller: :logins, step: :enter_username
28
+
29
+ fill_in :username, with: 'alice'
30
+ click_on :submit
31
+
32
+ assert_userland_view controller: :logins, step: :remote_session_available
33
+
34
+ code = find('[data-booth="code"]').text
35
+ end
36
+
37
+ # Redeem remote code
38
+
39
+ visit_namespaced controller: :remote_logins, action: :show
40
+
41
+ assert_userland_view controller: :remote_logins, step: :remote_login
42
+
43
+ fill_in :code, with: code
44
+ click_on :submit
45
+
46
+ # ------ SIGNIFICANT TEST ------
47
+ # Remote logins can be redeemed.
48
+ # ------------------------------
49
+ assert_userland_view controller: :remote_logins, step: :remote_solved
50
+
51
+ using_session(:other_device) do
52
+ visit_namespaced controller: :webauths, action: :index
53
+
54
+ # ------------ SIGNIFICANT TEST --------------
55
+ # Webauth sudo is required after remote login.
56
+ # --------------------------------------------
57
+ assert_userland_view controller: :webauths, step: :sudo
58
+ end
59
+
60
+ using_session(:yet_another_device) do
61
+ visit_namespaced controller: :logins, action: :new
62
+
63
+ assert_userland_view controller: :logins, step: :enter_username
64
+
65
+ fill_in :username, with: 'alice'
66
+ click_on :submit
67
+
68
+ assert_userland_view controller: :logins, step: :remote_session_available
69
+
70
+ ::Booth::Models::Remote.sole.update!(created_at: 21.minutes.ago)
71
+
72
+ visit_namespaced controller: :logins, action: :new
73
+
74
+ assert_userland_view controller: :logins, step: :remote_session_expired
75
+
76
+ travel 19.minutes
77
+
78
+ visit_namespaced controller: :logins, action: :new
79
+
80
+ # --- SIGNIFICANT TEST ----
81
+ # Remote logins can expire.
82
+ # -------------------------
83
+ assert_userland_view controller: :logins, step: :remote_session_expired
84
+
85
+ travel 2.minutes
86
+
87
+ visit_namespaced controller: :logins, action: :new
88
+
89
+ assert_userland_view controller: :logins, step: :enter_username
90
+
91
+ # ---------- SIGNIFICANT TEST -----------
92
+ # Login procedures as a whole can expire.
93
+ # ---------------------------------------
94
+ assert_text(/20 min/i) # Flash message
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Testing
5
+ module Userland
6
+ class OnboardingFirstTime < ::Booth::Testing::IncorporationTestCase
7
+ def call
8
+ before_test&.call
9
+
10
+ alice = ::Booth::Models::Credential.create!(
11
+ domain: ::Capybara.app_host.remove('http://'),
12
+ username: 'alice',
13
+ scope:,
14
+ )
15
+
16
+ after_credential&.call(credential_id: alice.id)
17
+
18
+ bobby = ::Booth::Models::Credential.create!(
19
+ domain: ::Capybara.app_host.remove('http://'),
20
+ username: 'bobby',
21
+ scope:,
22
+ )
23
+
24
+ after_credential&.call(credential_id: bobby.id)
25
+
26
+ alices_onboarding = ::Booth::Models::Onboarding.create!(
27
+ credential_id: alice.id,
28
+ )
29
+
30
+ bobbys_onboarding = ::Booth::Models::Onboarding.create!(
31
+ credential_id: bobby.id,
32
+ )
33
+
34
+ # Onboard via URL
35
+
36
+ visit_namespaced controller: :onboardings, action: :show,
37
+ params: { id: alices_onboarding.secret_key }
38
+
39
+ assert_userland_view controller: :onboardings, step: :redeem
40
+
41
+ click_on :submit
42
+
43
+ # ------------------------ SIGNIFICANT TEST --------------------------------
44
+ # Re-visiting the Onboarding later, shows the success of having redeemed it.
45
+ # --------------------------------------------------------------------------
46
+ assert_userland_view controller: :onboardings, step: :success
47
+
48
+ visit_namespaced controller: :webauths, action: :index
49
+
50
+ # ----- SIGNIFICANT TEST -----
51
+ # Onboarding logs the user in.
52
+ # ----------------------------
53
+ assert_userland_view controller: :webauths, step: :index
54
+
55
+ visit_namespaced controller: :onboardings, action: :show,
56
+ params: { id: alices_onboarding.secret_key }
57
+
58
+ assert_userland_view controller: :onboardings, step: :success
59
+
60
+ visit_namespaced controller: :onboardings, action: :show,
61
+ params: { id: bobbys_onboarding.secret_key }
62
+
63
+ # -------------------- SIGNIFICANT TEST -------------------
64
+ # Cannot onboard while the wrong user is already logged in.
65
+ # ---------------------------------------------------------
66
+ assert_userland_view controller: :onboardings, step: :wrong_user
67
+
68
+ soft_reset_session
69
+
70
+ visit_namespaced controller: :onboardings, action: :show,
71
+ params: { id: alices_onboarding.secret_key }
72
+
73
+ # ------------ SIGNIFICANT TEST ---------------
74
+ # Cannot redeem an already consumed Onboarding.
75
+ # ---------------------------------------------
76
+ assert_userland_view controller: :onboardings, step: :already_used
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end