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,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Sessions
6
+ # A Passport is an immutable representation of your current browser-session.
7
+ # For convenience, we add information about the Credential belonging to that session.
8
+ class ToPassport
9
+ include Calls
10
+ include ::Booth::Logging
11
+
12
+ param :session
13
+
14
+ def call
15
+ ::Booth::ToStruct.call(attributes)
16
+ end
17
+
18
+ private
19
+
20
+ delegate :credential, to: :session, private: true
21
+
22
+ def attributes
23
+ {
24
+ username: credential.username,
25
+ session_id: session.id,
26
+ credential_id: credential.id,
27
+ incognito_credential_id: session.incognito_credential_id,
28
+ blocked: credential.blocked?,
29
+ revoked: session.revoked_at.present?,
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Webauth
6
+ class AuthenticationVerification
7
+ include Calls
8
+ include ::Booth::Logging
9
+
10
+ option :request
11
+ option :credential_id
12
+ option :challenge
13
+
14
+ def call
15
+ if credential_id != authenticator.credential_id
16
+ raise 'this authenticator doesnt match the credential'
17
+ end
18
+
19
+ log do
20
+ "Verifying using challenge #{challenge.inspect} and public key #{authenticator.public_key.inspect} and sign count #{authenticator.sign_count.inspect}"
21
+ end
22
+ webauth.verify(
23
+ challenge,
24
+ public_key: authenticator.public_key,
25
+ sign_count: authenticator.sign_count,
26
+ )
27
+ log { 'Response successfully verified' }
28
+
29
+ authenticator.update!(sign_count: webauth.sign_count)
30
+ sudo.webauth!
31
+
32
+ Tron.success :webauth_authentication_verification_successful,
33
+ credential: authenticator.credential,
34
+ public_json: {},
35
+ http_status: :created
36
+ rescue WebAuthn::SignCountVerificationError => e
37
+ log { "Response verification failed: #{e.message} (expected #{authenticator.sign_count})" }
38
+ # TODO: Audit
39
+ Tron.failure :webauth_failed, public_json: { public_message: 'Passkey Sign count mismatch.' },
40
+ public_message: "Verification failed: #{e.message}",
41
+ # expected_sign_count: authenticator.sign_count,
42
+ http_status: :unprocessable_entity
43
+ rescue WebAuthn::Error => e
44
+ log { "Response verification failed: #{e.message}" }
45
+ # TODO: Audit but don't throttle?
46
+ Tron.failure :webauth_failed, public_json: {},
47
+ public_message: "Verification failed: #{e.message}",
48
+ http_status: :unprocessable_entity
49
+ rescue RuntimeError => e
50
+ # This happens e.g. if the params[:id] does not match the params[:rawId]
51
+ raise
52
+ ensure
53
+ sudo.webauthn_challenge = nil
54
+ end
55
+
56
+ private
57
+
58
+ delegate :sudo, to: :request
59
+
60
+ def webauth
61
+ ::WebAuthn::Credential.from_get(request.params[:handshake], relying_party:)
62
+ end
63
+
64
+ def authenticator
65
+ device_id = ::WebAuthn.standard_encoder.encode(webauth.raw_id)
66
+ @authenticator ||= ::Booth::Models::Authenticator.where(credential_id:)
67
+ .find_by!(device_id:)
68
+ end
69
+
70
+ def relying_party
71
+ ::Booth.config.relying_party_resolver.call(request:)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Webauth
6
+ class OptionsForCreate
7
+ include Calls
8
+ include ::Booth::Logging
9
+
10
+ option :webauthn_id
11
+ option :username
12
+ option :request
13
+ option :device_ids_to_exclude, default: -> {}
14
+
15
+ def call
16
+ unless relying_party
17
+ log { "Could not resolve relying party for #{request.host}" }
18
+ return Tron.failure(:missing_relying_party, challenge: nil, as_json: nil,
19
+ relying_party_id: nil)
20
+ end
21
+
22
+ options = ::WebAuthn::Credential.options_for_create(
23
+ user: {
24
+ id: webauthn_id,
25
+ name: username,
26
+ # Some browsers also support `display_name: "..."`
27
+ },
28
+ # Tell security key to also send its certificate as so called "attachment".
29
+ attestation: 'direct',
30
+ # Completely passwordless authentication should always require interaction/verification.
31
+ authenticator_selection: { user_verification: :required },
32
+ relying_party:,
33
+
34
+ # The advantage of excluding already registered devices is that we avoid duplicates.
35
+ # The disadvantage is, when the user manually resets the hardware device.
36
+ # Then the (now pristine) device cannot be used, because the orphan is still in the DB.
37
+ # In that case the user has to delete the orphan first, which seems okay.
38
+ exclude: device_ids_to_exclude,
39
+ )
40
+
41
+ Tron.success(:webauthn_options_for_create, challenge: options.challenge,
42
+ as_json: options.as_json,
43
+ relying_party_id: options.relying_party&.id)
44
+ end
45
+
46
+ private
47
+
48
+ def relying_party
49
+ return @relying_party if defined?(@relying_party)
50
+
51
+ @relying_party = ::Booth.config.relying_party_resolver.call(request:)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Webauth
6
+ class OptionsForGet
7
+ include Calls
8
+
9
+ option :allowed_device_ids
10
+ option :request
11
+
12
+ def call
13
+ raise 'what' unless relying_party
14
+
15
+ WebAuthn::Credential.options_for_get(
16
+ allow: allowed_device_ids,
17
+ user_verification: :required,
18
+ relying_party:,
19
+ )
20
+ end
21
+
22
+ private
23
+
24
+ def relying_party
25
+ ::Booth.config.relying_party_resolver.call(request:)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Webauth
6
+ # Queries the semi-official AAGUID database for passkey vendors.
7
+ class Provider
8
+ def self.find(aaguid)
9
+ attributes = all[aaguid.to_sym]
10
+ return unless attributes
11
+
12
+ attributes.merge!(aaguid:)
13
+
14
+ ::Data.define(:aaguid, :name, :icon_dark, :icon_light).new(**attributes)
15
+ end
16
+
17
+ private
18
+
19
+ def self.all
20
+ @all ||= load_aaguids
21
+ end
22
+ private_class_method :all
23
+
24
+ def self.load_aaguids
25
+ # This file is downloaded via a rake task from
26
+ # https://github.com/passkeydeveloper/passkey-authenticator-aaguids
27
+ path = Pathname.new('../../../../data/combined_aaguid.json').expand_path(__dir__)
28
+ raise "Missing AAGUIDs file: #{path}" unless path.exist?
29
+
30
+ JSON.parse(path.read, symbolize_names: true)
31
+ end
32
+ private_class_method :load_aaguids
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Core
5
+ module Webauth
6
+ # Verifies the ajax-response of a hardware key to a previous challenge.
7
+ class RegistrationVerification
8
+ include Calls
9
+ include ::Booth::Logging
10
+
11
+ option :credential_id
12
+ option :challenge
13
+ option :handshake
14
+ option :request
15
+
16
+ def call
17
+ do_verify_response
18
+ .on_success { do_return_attributes }
19
+ end
20
+
21
+ private
22
+
23
+ def do_verify_response
24
+ log { "Verifying challenge #{challenge.inspect} using handshake #{handshake}" }
25
+ webauth.verify(challenge)
26
+ log { 'Response successfully verified' }
27
+
28
+ Tron.success :webauth_registration_verification_successful
29
+ rescue WebAuthn::AttestationStatementVerificationError => e
30
+ # Only happens when `::WebAuthn.config.verify_attestation_statement = true`
31
+ log { "Attestation Verification failed - #{e.message}" }
32
+ Tron.failure :attestation_verification_failed,
33
+ public_json: { public_message: 'Attestation Statement Verification failed' },
34
+ http_status: :bad_request
35
+
36
+ # rescue WebAuthn::SignCountVerificationError => e
37
+ # At Registration this should never happen.
38
+ # Because the sign_counter will always be 0 (or 1?).
39
+ rescue WebAuthn::OriginVerificationError => e
40
+ log { 'Request must come from allowed origin' }
41
+ Tron.failure :origin_verification_failed,
42
+ http_status: :unprocessable_entity,
43
+ public_json: {
44
+ public_message: "The origin hostname/port could not be verified (#{e.message})"
45
+ }
46
+ rescue WebAuthn::Error => e
47
+ log { "Webauth Handshake failed: #{e.message}" }
48
+ Tron.failure :invalid_challenge_response
49
+ # TODO: Audit but don't throttle?
50
+ Tron.failure :webauth_failed,
51
+ http_status: :unprocessable_entity,
52
+ public_json: {
53
+ public_message: "Verification failed: #{e.message}"
54
+ }
55
+ rescue RuntimeError => e
56
+ # This happens e.g. if the params[:id] does not match the params[:rawId]
57
+ log { "Registration Verification failed - #{e.class} #{e.message}" }
58
+ Tron.failure(:registration_verification_failed, http_status: :bad_request,
59
+ public_json: { public_message: e.message })
60
+ end
61
+
62
+ def do_return_attributes
63
+ Tron.success :registration_verification_successful,
64
+ device_id:,
65
+ public_key: webauth.public_key,
66
+ attachment: webauth.authenticator_attachment,
67
+ sign_count: webauth.sign_count,
68
+ aaguid:,
69
+ issuer:,
70
+ confirmed_at: Time.current
71
+ end
72
+
73
+ def aaguid
74
+ webauth.response.attestation_object.aaguid
75
+ end
76
+
77
+ def device_id
78
+ ::WebAuthn.standard_encoder.encode(webauth.raw_id.to_s)
79
+ end
80
+
81
+ def issuer
82
+ webauth.response
83
+ .attestation_object
84
+ .attestation_statement
85
+ .attestation_certificate
86
+ &.subject
87
+ &.to_utf8
88
+ end
89
+
90
+ def webauth
91
+ @webauth ||= ::WebAuthn::Credential.from_create(handshake, relying_party:)
92
+ end
93
+
94
+ def relying_party
95
+ ::Booth.config.relying_party_resolver.call(request:)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ # Public read-only Model you can call in your Rails app.
5
+ #
6
+ # We expose read-only Credentials without associations for DB querying and joins
7
+ # that the Rails developer might like to perform. E.g. Customer.joins(:credential)
8
+ # This is the only exception, other records will be queried through Adminland#some_method
9
+ #
10
+ # You can join this in your models the usual way:
11
+ #
12
+ # class Customer < ApplicationRecord
13
+ # belongs_to :credential, class_name: '::Booth::Credential'
14
+ # end
15
+ #
16
+ # You can even subclass it:
17
+ #
18
+ # class Credential < Booth::Credential
19
+ # has_one :customer, dependent: :nullify
20
+ # end
21
+ #
22
+ # So that you can run join queries:
23
+ #
24
+ # Credential.where(domain: 'example.com', scope: :community).where.missing(:customer)
25
+ #
26
+ # You may also reference this table in migrations:
27
+ #
28
+ # add_foreign_key :customers, :booth_credentials, column: :credential_id, on_delete: :nullify
29
+ #
30
+ class Credential < ActiveRecord::Base # rubocop:disable Rails/ApplicationRecord
31
+ self.table_name = 'booth_credentials'
32
+
33
+ after_initialize :readonly!
34
+ end
35
+ end
data/lib/booth/engine.rb CHANGED
@@ -1,14 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Booth
4
+ # The Booth Engine you can use in your Rails app.
2
5
  class Engine < ::Rails::Engine
3
- initializer 'booth.add_manifest' do |app|
4
- app.config.assets.precompile << 'booth_manifest.js'
6
+ config.autoload_paths << root.join('lib')
7
+
8
+ initializer 'booth.importmap', before: 'importmap' do |app|
9
+ app.config.importmap.paths << Engine.root.join('config/importmap.rb')
10
+ # Watch JS changes in development
11
+ app.config.importmap.cache_sweepers << Engine.root.join('app/assets/javascripts')
12
+ end
13
+
14
+ initializer 'booth.assets' do |app|
15
+ app.config.assets.paths << Engine.root.join('app/javascript')
5
16
  end
6
17
 
7
18
  initializer 'booth.insert_warden_middleware' do |app|
8
19
  app.config.middleware.insert_after ::ActionDispatch::Flash, ::Warden::Manager do |manager|
9
20
  manager.intercept_401 = false
10
- manager.serialize_into_session { ::Booth::Hooks::SerializeIntoSession.call(_1) }
11
- manager.serialize_from_session { ::Booth::Hooks::SerializeFromSession.call(_1) }
21
+ manager.serialize_into_session { ::Booth::Hooks::SerializeIntoSession.call(it) }
22
+ manager.serialize_from_session { ::Booth::Hooks::SerializeFromSession.call(it) }
12
23
  end
13
24
  end
14
25
 
data/lib/booth/errors.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Booth
2
4
  module Errors
3
5
  class Error < ::StandardError
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Booth
2
4
  module Hooks
5
+ # Once Warden fetched the session from DB, let's check it for authorization.
3
6
  class AfterFetch
4
- include ::Booth::MethodObject
7
+ include Calls
5
8
  include ::Booth::Logging
6
9
 
7
10
  option :passport
@@ -9,11 +12,16 @@ module Booth
9
12
  option :options
10
13
 
11
14
  def call
12
- if passport.revoked_at
13
- debug { "Your session in scope #{scope.inspect} was revoked at #{passport.revoked_at}. Logging you out." }
15
+ if passport.blocked
16
+ log { 'Your credential is blocked. Logging you out.' }
17
+ request.authentication.logout
18
+
19
+ elsif passport.revoked
20
+ log { "Your session in scope #{scope.inspect} was revoked. Logging you out." }
14
21
  request.authentication.logout
22
+
15
23
  else
16
- debug { "Registering activity of legitimate session in scope #{scope.inspect} with IP #{request.ip}..." }
24
+ log { "Registering activity of legitimate session in scope #{scope.inspect} with IP #{request.ip}..." }
17
25
  register_activity
18
26
  end
19
27
 
@@ -23,7 +31,7 @@ module Booth
23
31
  private
24
32
 
25
33
  def register_activity
26
- ::Booth::Models::Session.where(id: passport.id).update_all(
34
+ ::Booth::Models::Session.where(id: passport.session_id).update_all(
27
35
  ['activity_at = ?, ' \
28
36
  'agent = ?, ' \
29
37
  'location = ?, ' \
@@ -37,7 +45,7 @@ module Booth
37
45
  request.ip,
38
46
  request.location,
39
47
  request.ip,
40
- Time.current.to_i.to_s]
48
+ Time.current.to_i.to_s],
41
49
  )
42
50
  end
43
51
 
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Booth
2
4
  module Hooks
3
5
  class BeforeLogout
4
- include ::Booth::MethodObject
6
+ include Calls
5
7
  include ::Booth::Logging
6
8
 
7
9
  option :passport
@@ -11,9 +13,9 @@ module Booth
11
13
  def call
12
14
  return unless passport # Attempting to logout when already logged out.
13
15
 
14
- debug { "Revoking Session with ID #{passport.id} in scope #{scope.inspect} because of logout" }
16
+ log { "Revoking Session #{passport.session_id} in scope #{scope.inspect} because of logout" }
15
17
 
16
- ::Booth::Models::Session.where(id: passport.id)
18
+ ::Booth::Models::Session.where(id: passport.session_id)
17
19
  .update_all(revoked_at: Time.current, revoke_reason: :logout)
18
20
 
19
21
  nil
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Booth
2
4
  module Hooks
5
+ # Convert what is in the cookie to a session record from the DB.
3
6
  class SerializeFromSession
4
- include ::Booth::MethodObject
7
+ include Calls
5
8
  include ::Booth::Logging
6
9
 
7
10
  param :session_id
@@ -9,15 +12,20 @@ module Booth
9
12
  def call
10
13
  ::Booth::Syntaxes::Uuid.call(session_id, raise_if_invalid: true)
11
14
 
12
- session = ::Booth::Models::Session.active_scope.find_by(id: session_id)
15
+ session = ::Booth::Models::Session.active_scope
16
+ .includes_scope
17
+ .find_by(id: session_id)
13
18
 
14
19
  unless session
15
- debug { "Session ID #{session_id.inspect} stored in the cookie, but doesn't exist in the database" }
20
+ log { "Session ID #{session_id.inspect} stored in cookie doesn't exist in database" }
21
+
22
+ # p ::Booth::Models::Session.find_by(id: session_id)
23
+
16
24
  return
17
25
  end
18
26
 
19
- debug { "Deserializing Session #{session_id.inspect} information into Warden..." }
20
- ::Booth::Sessions::ToPassport.call(session)
27
+ log { "Deserializing DB Session #{session_id.inspect} information into Warden cookie..." }
28
+ ::Booth::Core::Sessions::ToPassport.call(session)
21
29
  end
22
30
  end
23
31
  end
@@ -1,13 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Booth
2
4
  module Hooks
5
+ # Persist an authenticated session in the cookie.
3
6
  class SerializeIntoSession
4
- include ::Booth::MethodObject
7
+ include Calls
5
8
 
6
9
  param :passport
7
10
 
8
11
  def call
9
- # This is the ID of the `Booth::Models::Session`.
10
- passport.id
12
+ # This is the ID of the `Booth::Models::Session` record.
13
+ passport.session_id
11
14
  end
12
15
  end
13
16
  end
data/lib/booth/logging.rb CHANGED
@@ -1,59 +1,30 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Booth
2
4
  module Logging
3
5
  extend ActiveSupport::Concern
4
6
 
5
- # TODO: Implement only generic `log` method to avoid cluttering the Object where this was included.
6
- # We only use `debug` anyway.
7
7
  class_methods do
8
- def debug(&)
9
- _logger.debug(&)
10
- end
11
-
12
- def info(&)
13
- _logger.info(&)
14
- end
15
-
16
- def warn(&)
17
- _logger.warn(&)
18
- end
19
-
20
- def error(&)
21
- _logger.error(&)
22
- end
23
-
24
- def fatal(&)
25
- _logger.fatal(&)
8
+ def log(&)
9
+ ::Booth.config.logger&.debug(to_s, &)
26
10
  end
27
11
 
28
- def _logger
29
- ::Booth::Logger.new(self)
12
+ def emit(event_name, data = {})
13
+ # See https://github.com/rails/rails/blob/main/activesupport/lib/active_support/event_reporter.rb
14
+ ::Rails.event.notify("booth.#{event_name}", data, caller_depth: 2)
15
+ nil
30
16
  end
31
17
  end
32
18
 
33
19
  private
34
20
 
35
- def debug(&)
36
- _logger.debug(&)
37
- end
38
-
39
- def info(&)
40
- _logger.info(&)
41
- end
42
-
43
- def warn(&)
44
- _logger.warn(&)
45
- end
46
-
47
- def error(&)
48
- _logger.error(&)
49
- end
50
-
51
- def fatal(&)
52
- _logger.fatal(&)
21
+ def log(&)
22
+ ::Booth.config.logger&.debug(self.class.to_s, &)
53
23
  end
54
24
 
55
- def _logger
56
- @_logger ||= ::Booth::Logger.new(self)
25
+ def emit(event_name, data = {})
26
+ ::Rails.event.notify("booth.#{event_name}", data, caller_depth: 2)
27
+ nil
57
28
  end
58
29
  end
59
30
  end
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Booth
2
4
  module Models
5
+ # All ActiveRecord models inherit from this class.
3
6
  class ApplicationRecord < ::ActiveRecord::Base
4
7
  self.abstract_class = true
5
8
  end
@@ -1,20 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Booth
2
4
  module Models
5
+ # An event log.
3
6
  class Audit < ::Booth::Models::ApplicationRecord
4
7
  self.table_name = 'booth_audits'
5
8
 
6
- enum event: { added_otp: 'added_otp',
7
- changed_otp: 'changed_otp',
8
- completed_onboarding: 'completed_onboarding',
9
- entered_correct_password: 'entered_correct_password',
10
- entered_wrong_otp: 'entered_wrong_otp',
11
- entered_wrong_password: 'entered_wrong_password',
12
- logout: 'logout',
13
- queried_unknown_username: 'queried_unknown_username',
14
- requested_password_reset: 'requested_password_reset' },
15
- _prefix: true
9
+ # changed_tenant (payload from: to:)
10
+ enum :event, %i[
11
+ completed_onboarding
12
+ logout
13
+ registered
14
+ ].index_with(&:to_s), prefix: true
16
15
 
17
- belongs_to :credential, class_name: '::Booth::Models::Credential', optional: true
16
+ belongs_to :credential, optional: true
18
17
 
19
18
  validates :ip, :event, presence: true
20
19