booth 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +4 -0
  3. data/LICENSE.md +22 -0
  4. data/README.md +372 -0
  5. data/app/assets/config/booth_manifest.js +15 -0
  6. data/app/assets/images/booth/browsers/README.md +2 -0
  7. data/app/assets/images/booth/browsers/chrome.svg +1 -0
  8. data/app/assets/images/booth/browsers/edge.svg +1 -0
  9. data/app/assets/images/booth/browsers/firefox.svg +1 -0
  10. data/app/assets/images/booth/browsers/internet_explorer.svg +1 -0
  11. data/app/assets/images/booth/browsers/opera.svg +1 -0
  12. data/app/assets/images/booth/browsers/safari.svg +1 -0
  13. data/app/assets/images/booth/browsers/unknown.svg +1 -0
  14. data/app/assets/images/booth/platforms/README.md +2 -0
  15. data/app/assets/images/booth/platforms/android.svg +6 -0
  16. data/app/assets/images/booth/platforms/apple.svg +6 -0
  17. data/app/assets/images/booth/platforms/linux.svg +6 -0
  18. data/app/assets/images/booth/platforms/unknown.svg +1 -0
  19. data/app/assets/images/booth/platforms/windows.svg +6 -0
  20. data/app/assets/javascripts/booth/all.js +162 -0
  21. data/app/assets/javascripts/booth/all.js.map +1 -0
  22. data/app/assets/javascripts/booth/booth.ts +194 -0
  23. data/app/assets/javascripts/booth/webauthn-json.ts +99 -0
  24. data/config/locales/de.yml +84 -0
  25. data/config/locales/en.yml +79 -0
  26. data/lib/booth/adminland/credentials/create.rb +30 -0
  27. data/lib/booth/adminland/onboardings/create.rb +63 -0
  28. data/lib/booth/adminland/onboardings/destroy.rb +50 -0
  29. data/lib/booth/adminland/onboardings/find.rb +93 -0
  30. data/lib/booth/adminland/onboardings/index.rb +23 -0
  31. data/lib/booth/adminland/periodic_cleanup.rb +11 -0
  32. data/lib/booth/adminland/recoveries/consume.rb +70 -0
  33. data/lib/booth/adminland.rb +48 -0
  34. data/lib/booth/audits/register/added_otp.rb +22 -0
  35. data/lib/booth/audits/register/changed_otp.rb +22 -0
  36. data/lib/booth/audits/register/completed_onboarding.rb +22 -0
  37. data/lib/booth/audits/register/correct_otp.rb +42 -0
  38. data/lib/booth/audits/register/correct_password.rb +43 -0
  39. data/lib/booth/audits/register/logout.rb +22 -0
  40. data/lib/booth/audits/register/requested_password_reset.rb +22 -0
  41. data/lib/booth/audits/register/wrong_otp.rb +22 -0
  42. data/lib/booth/audits/register/wrong_password.rb +25 -0
  43. data/lib/booth/authenticators/confirm.rb +34 -0
  44. data/lib/booth/authenticators/credential_mode_after_confirmation.rb +25 -0
  45. data/lib/booth/authenticators/step.rb +19 -0
  46. data/lib/booth/concerns/action.rb +58 -0
  47. data/lib/booth/concerns/transition.rb +17 -0
  48. data/lib/booth/configuration.rb +116 -0
  49. data/lib/booth/configure.rb +37 -0
  50. data/lib/booth/contests/get.rb +36 -0
  51. data/lib/booth/contests/respond.rb +78 -0
  52. data/lib/booth/contests/set_for_login.rb +28 -0
  53. data/lib/booth/cooldowns/distance_of_time.rb +46 -0
  54. data/lib/booth/cooldowns/otp.rb +22 -0
  55. data/lib/booth/cooldowns/password.rb +44 -0
  56. data/lib/booth/cooldowns/password_reset.rb +24 -0
  57. data/lib/booth/cooldowns/strategies/exponential.rb +82 -0
  58. data/lib/booth/cooldowns/strategies/global.rb +62 -0
  59. data/lib/booth/cooldowns/strategies/result.rb +22 -0
  60. data/lib/booth/credentials/create.rb +28 -0
  61. data/lib/booth/credentials/create_with_onboarding.rb +26 -0
  62. data/lib/booth/credentials/find_by_username.rb +45 -0
  63. data/lib/booth/credentials/mode.rb +69 -0
  64. data/lib/booth/credentials/modes/otp_addable.rb +23 -0
  65. data/lib/booth/credentials/modes/otp_changeable.rb +23 -0
  66. data/lib/booth/credentials/modes/otp_manageable.rb +17 -0
  67. data/lib/booth/credentials/modes/otp_removable.rb +23 -0
  68. data/lib/booth/credentials/modes/password_addable.rb +29 -0
  69. data/lib/booth/credentials/modes/password_changeable.rb +31 -0
  70. data/lib/booth/credentials/modes/password_manageable.rb +17 -0
  71. data/lib/booth/credentials/modes/password_removable.rb +24 -0
  72. data/lib/booth/credentials/modes/password_removal_requires_user_verifiable_webauth.rb +16 -0
  73. data/lib/booth/credentials/modes/webauth_addable.rb +26 -0
  74. data/lib/booth/credentials/modes/webauth_manageable.rb +16 -0
  75. data/lib/booth/credentials/modes/webauth_removable.rb +25 -0
  76. data/lib/booth/credentials/otp_authentication.rb +59 -0
  77. data/lib/booth/credentials/password_authentication.rb +72 -0
  78. data/lib/booth/credentials/webauth_challenge.rb +28 -0
  79. data/lib/booth/engine.rb +25 -0
  80. data/lib/booth/errors.rb +86 -0
  81. data/lib/booth/geolocation.rb +20 -0
  82. data/lib/booth/hooks/after_fetch.rb +54 -0
  83. data/lib/booth/hooks/before_logout.rb +29 -0
  84. data/lib/booth/hooks/serialize_from_session.rb +24 -0
  85. data/lib/booth/hooks/serialize_into_session.rb +14 -0
  86. data/lib/booth/logger.rb +41 -0
  87. data/lib/booth/logging.rb +59 -0
  88. data/lib/booth/method_object.rb +73 -0
  89. data/lib/booth/mode.rb +22 -0
  90. data/lib/booth/models/application_record.rb +7 -0
  91. data/lib/booth/models/audit.rb +24 -0
  92. data/lib/booth/models/authenticator.rb +45 -0
  93. data/lib/booth/models/concerns/modeable.rb +50 -0
  94. data/lib/booth/models/concerns/otpable.rb +37 -0
  95. data/lib/booth/models/concerns/passwordable.rb +58 -0
  96. data/lib/booth/models/contest.rb +55 -0
  97. data/lib/booth/models/contests/scopes/recently_created.rb +23 -0
  98. data/lib/booth/models/contests/scopes/recently_responded.rb +32 -0
  99. data/lib/booth/models/credential.rb +61 -0
  100. data/lib/booth/models/onboarding.rb +61 -0
  101. data/lib/booth/models/password_reset.rb +41 -0
  102. data/lib/booth/models/recovery.rb +32 -0
  103. data/lib/booth/models/registration.rb +10 -0
  104. data/lib/booth/models/session.rb +47 -0
  105. data/lib/booth/models/user_agent.rb +50 -0
  106. data/lib/booth/modes/base.rb +25 -0
  107. data/lib/booth/modes/username_and_password.rb +7 -0
  108. data/lib/booth/modes/username_and_webauth.rb +7 -0
  109. data/lib/booth/modes/username_password_and_otp.rb +7 -0
  110. data/lib/booth/modes/username_password_and_webauth.rb +7 -0
  111. data/lib/booth/onboardings/find.rb +35 -0
  112. data/lib/booth/onboardings/propagate_to_credential.rb +63 -0
  113. data/lib/booth/onboardings/step.rb +68 -0
  114. data/lib/booth/password_resets/create.rb +57 -0
  115. data/lib/booth/password_resets/find.rb +36 -0
  116. data/lib/booth/password_resets/propagate_to_credential.rb +36 -0
  117. data/lib/booth/password_resets/step.rb +18 -0
  118. data/lib/booth/recoveries/create.rb +45 -0
  119. data/lib/booth/request.rb +106 -0
  120. data/lib/booth/requests/agent.rb +14 -0
  121. data/lib/booth/requests/authentication.rb +47 -0
  122. data/lib/booth/requests/ip.rb +28 -0
  123. data/lib/booth/requests/return_path.rb +34 -0
  124. data/lib/booth/requests/session.rb +106 -0
  125. data/lib/booth/requests/storage.rb +62 -0
  126. data/lib/booth/requests/storages/login.rb +108 -0
  127. data/lib/booth/requests/storages/otp.rb +54 -0
  128. data/lib/booth/requests/storages/password.rb +49 -0
  129. data/lib/booth/requests/storages/password_reset.rb +35 -0
  130. data/lib/booth/requests/storages/recovery.rb +35 -0
  131. data/lib/booth/requests/storages/registration.rb +27 -0
  132. data/lib/booth/requests/storages/webauth.rb +38 -0
  133. data/lib/booth/requests/sudo.rb +110 -0
  134. data/lib/booth/routes/userland.rb +80 -0
  135. data/lib/booth/sessions/create_and_login.rb +46 -0
  136. data/lib/booth/sessions/historical_locations.rb +18 -0
  137. data/lib/booth/sessions/index.rb +59 -0
  138. data/lib/booth/sessions/revoke.rb +51 -0
  139. data/lib/booth/sessions/revoke_all_others.rb +43 -0
  140. data/lib/booth/sessions/to_passport.rb +51 -0
  141. data/lib/booth/syntaxes/contest_code.rb +58 -0
  142. data/lib/booth/syntaxes/email.rb +97 -0
  143. data/lib/booth/syntaxes/ip.rb +37 -0
  144. data/lib/booth/syntaxes/otp.rb +57 -0
  145. data/lib/booth/syntaxes/scope.rb +21 -0
  146. data/lib/booth/syntaxes/scope_comparison.rb +28 -0
  147. data/lib/booth/syntaxes/secret_key.rb +64 -0
  148. data/lib/booth/syntaxes/username.rb +85 -0
  149. data/lib/booth/syntaxes/uuid.rb +23 -0
  150. data/lib/booth/test/helpers.rb +63 -0
  151. data/lib/booth/test/support/assert_all_partials_were_covered.rb +63 -0
  152. data/lib/booth/test/support/assert_logged_in.rb +49 -0
  153. data/lib/booth/test/support/assert_logged_out.rb +30 -0
  154. data/lib/booth/test/support/assert_partial.rb +29 -0
  155. data/lib/booth/test/support/force_login.rb +26 -0
  156. data/lib/booth/test/support/get_session_value.rb +35 -0
  157. data/lib/booth/test/support/otp_code_from_session.rb +30 -0
  158. data/lib/booth/test/support/soft_reset_session.rb +22 -0
  159. data/lib/booth/test/userland/logins/missing_authenticators.rb +72 -0
  160. data/lib/booth/test/userland/logins/missing_onboarding.rb +35 -0
  161. data/lib/booth/test/userland/logins/username_and_password.rb +40 -0
  162. data/lib/booth/test/userland/logins/username_and_webauth.rb +75 -0
  163. data/lib/booth/test/userland/logins/username_password_and_otp.rb +45 -0
  164. data/lib/booth/test/userland/logins/username_password_and_webauth.rb +86 -0
  165. data/lib/booth/test/userland/onboardings/already_logged_in.rb +64 -0
  166. data/lib/booth/test/userland/onboardings/otp.rb +63 -0
  167. data/lib/booth/test/userland/onboardings/password.rb +49 -0
  168. data/lib/booth/test/userland/onboardings/timeout.rb +47 -0
  169. data/lib/booth/test/userland/otps/manage.rb +86 -0
  170. data/lib/booth/test/userland/password_resets/reset.rb +102 -0
  171. data/lib/booth/test/userland.rb +38 -0
  172. data/lib/booth/test/webauthn/disable.rb +17 -0
  173. data/lib/booth/test/webauthn/enable.rb +19 -0
  174. data/lib/booth/test/webauthn/virtual_authenticators/create.rb +38 -0
  175. data/lib/booth/test/webauthn/virtual_authenticators/destroy.rb +20 -0
  176. data/lib/booth/test.rb +53 -0
  177. data/lib/booth/to_struct.rb +11 -0
  178. data/lib/booth/userland/extract_flash_messages.rb +35 -0
  179. data/lib/booth/userland/logins/create.rb +28 -0
  180. data/lib/booth/userland/logins/destroy.rb +37 -0
  181. data/lib/booth/userland/logins/new.rb +70 -0
  182. data/lib/booth/userland/logins/transitions/create/choose_username.rb +41 -0
  183. data/lib/booth/userland/logins/transitions/create/enter_otp.rb +70 -0
  184. data/lib/booth/userland/logins/transitions/create/skip_remotes.rb +24 -0
  185. data/lib/booth/userland/logins/transitions/create/verify_password.rb +70 -0
  186. data/lib/booth/userland/logins/transitions/create/webauth_authentication_initiation.rb +55 -0
  187. data/lib/booth/userland/logins/transitions/create/webauth_authentication_verification.rb +80 -0
  188. data/lib/booth/userland/logins/transitions/new/already_logged_in.rb +21 -0
  189. data/lib/booth/userland/logins/transitions/new/fallible.rb +27 -0
  190. data/lib/booth/userland/logins/transitions/new/mode_first_time.rb +20 -0
  191. data/lib/booth/userland/logins/transitions/new/mode_username_and_password.rb +20 -0
  192. data/lib/booth/userland/logins/transitions/new/mode_username_and_webauth.rb +26 -0
  193. data/lib/booth/userland/logins/transitions/new/mode_username_password_and_otp.rb +24 -0
  194. data/lib/booth/userland/logins/transitions/new/mode_username_password_and_webauth.rb +24 -0
  195. data/lib/booth/userland/logins/transitions/new/no_username_chosen.rb +19 -0
  196. data/lib/booth/userland/logins/transitions/new/remote_session_available.rb +52 -0
  197. data/lib/booth/userland/logins/transitions/new/timed_out.rb +25 -0
  198. data/lib/booth/userland/onboardings/show.rb +74 -0
  199. data/lib/booth/userland/onboardings/transitions/update/choose_mode.rb +58 -0
  200. data/lib/booth/userland/onboardings/transitions/update/choose_password.rb +41 -0
  201. data/lib/booth/userland/onboardings/transitions/update/choose_webauth_nickname.rb +50 -0
  202. data/lib/booth/userland/onboardings/transitions/update/confirm_otp.rb +58 -0
  203. data/lib/booth/userland/onboardings/transitions/update/confirm_password.rb +49 -0
  204. data/lib/booth/userland/onboardings/transitions/update/register_otp.rb +31 -0
  205. data/lib/booth/userland/onboardings/transitions/update/reset_otp.rb +40 -0
  206. data/lib/booth/userland/onboardings/transitions/update/reset_password.rb +35 -0
  207. data/lib/booth/userland/onboardings/transitions/update/reset_webauth.rb +46 -0
  208. data/lib/booth/userland/onboardings/transitions/update/webauth_authentication_initiation.rb +40 -0
  209. data/lib/booth/userland/onboardings/transitions/update/webauth_authentication_verification.rb +59 -0
  210. data/lib/booth/userland/onboardings/transitions/update/webauth_registration_initiation.rb +46 -0
  211. data/lib/booth/userland/onboardings/transitions/update/webauth_registration_verification.rb +56 -0
  212. data/lib/booth/userland/onboardings/update.rb +68 -0
  213. data/lib/booth/userland/otps/destroy.rb +42 -0
  214. data/lib/booth/userland/otps/edit.rb +72 -0
  215. data/lib/booth/userland/otps/guards/manageable.rb +21 -0
  216. data/lib/booth/userland/otps/guards/sudo.rb +23 -0
  217. data/lib/booth/userland/otps/show.rb +36 -0
  218. data/lib/booth/userland/otps/sudo.rb +51 -0
  219. data/lib/booth/userland/otps/transitions/update/confirm.rb +84 -0
  220. data/lib/booth/userland/otps/transitions/update/register.rb +40 -0
  221. data/lib/booth/userland/otps/transitions/update/reset.rb +31 -0
  222. data/lib/booth/userland/otps/update.rb +34 -0
  223. data/lib/booth/userland/password_resets/create.rb +73 -0
  224. data/lib/booth/userland/password_resets/guards/logged_out.rb +21 -0
  225. data/lib/booth/userland/password_resets/new.rb +57 -0
  226. data/lib/booth/userland/password_resets/show.rb +77 -0
  227. data/lib/booth/userland/password_resets/transitions/update/choose_password.rb +48 -0
  228. data/lib/booth/userland/password_resets/transitions/update/confirm_password.rb +54 -0
  229. data/lib/booth/userland/password_resets/transitions/update/reset_password.rb +29 -0
  230. data/lib/booth/userland/password_resets/update.rb +65 -0
  231. data/lib/booth/userland/passwords/destroy.rb +41 -0
  232. data/lib/booth/userland/passwords/edit.rb +54 -0
  233. data/lib/booth/userland/passwords/guards/manageable.rb +21 -0
  234. data/lib/booth/userland/passwords/guards/removable.rb +21 -0
  235. data/lib/booth/userland/passwords/guards/sudo.rb +21 -0
  236. data/lib/booth/userland/passwords/remove.rb +34 -0
  237. data/lib/booth/userland/passwords/show.rb +32 -0
  238. data/lib/booth/userland/passwords/sudo.rb +55 -0
  239. data/lib/booth/userland/passwords/transitions/remove/step.rb +27 -0
  240. data/lib/booth/userland/passwords/transitions/update/choose_password.rb +62 -0
  241. data/lib/booth/userland/passwords/transitions/update/confirm_password.rb +82 -0
  242. data/lib/booth/userland/passwords/update.rb +33 -0
  243. data/lib/booth/userland/personal_contests/show.rb +60 -0
  244. data/lib/booth/userland/personal_contests/update.rb +37 -0
  245. data/lib/booth/userland/recoveries/create.rb +48 -0
  246. data/lib/booth/userland/recoveries/new.rb +35 -0
  247. data/lib/booth/userland/registrations/create.rb +56 -0
  248. data/lib/booth/userland/registrations/new.rb +39 -0
  249. data/lib/booth/userland/sessions/destroy_one_or_other.rb +41 -0
  250. data/lib/booth/userland/sessions/index.rb +27 -0
  251. data/lib/booth/userland/sessions/show.rb +31 -0
  252. data/lib/booth/userland/sessions/transitions/destroy/enter_password.rb +50 -0
  253. data/lib/booth/userland/sessions/transitions/destroy/enter_webauth.rb +56 -0
  254. data/lib/booth/userland/sessions/transitions/destroy/verify_password.rb +83 -0
  255. data/lib/booth/userland/sessions/transitions/destroy/webauth_authentication_initiation.rb +38 -0
  256. data/lib/booth/userland/sessions/transitions/destroy/webauth_authentication_verification.rb +61 -0
  257. data/lib/booth/userland/sessions/transitions/show/enter_webauth.rb +56 -0
  258. data/lib/booth/userland/webauths/create.rb +83 -0
  259. data/lib/booth/userland/webauths/destroy.rb +60 -0
  260. data/lib/booth/userland/webauths/guards/manageable.rb +21 -0
  261. data/lib/booth/userland/webauths/guards/sudo.rb +22 -0
  262. data/lib/booth/userland/webauths/index.rb +43 -0
  263. data/lib/booth/userland/webauths/new.rb +70 -0
  264. data/lib/booth/userland/webauths/sudo.rb +25 -0
  265. data/lib/booth/userland/webauths/transitions/create/authentication_initiation.rb +52 -0
  266. data/lib/booth/userland/webauths/transitions/create/authentication_verification.rb +64 -0
  267. data/lib/booth/userland/webauths/transitions/create/choose_nickname.rb +50 -0
  268. data/lib/booth/userland/webauths/transitions/create/registration_initiation.rb +61 -0
  269. data/lib/booth/userland/webauths/transitions/create/registration_verification.rb +68 -0
  270. data/lib/booth/userland/webauths/transitions/create/reset.rb +36 -0
  271. data/lib/booth/userland/webauths/transitions/new/step.rb +23 -0
  272. data/lib/booth/userland/webauths/transitions/sudo/authentication_initiation.rb +47 -0
  273. data/lib/booth/userland/webauths/transitions/sudo/authentication_verification.rb +34 -0
  274. data/lib/booth/userland.rb +192 -0
  275. data/lib/booth/version.rb +3 -0
  276. data/lib/booth/webauth/authentication_verification.rb +68 -0
  277. data/lib/booth/webauth/demand_user_verification.rb +29 -0
  278. data/lib/booth/webauth/options_for_create.rb +46 -0
  279. data/lib/booth/webauth/options_for_get.rb +29 -0
  280. data/lib/booth.rb +267 -0
  281. data/lib/generators/booth/migration/migration_generator.rb +25 -0
  282. data/lib/generators/booth/migration/templates/add_credential_to_users.erb +18 -0
  283. data/lib/generators/booth/migration/templates/create_booth_mode_types.erb +20 -0
  284. data/lib/generators/booth/migration/templates/create_booth_tables.erb +135 -0
  285. metadata +861 -0
@@ -0,0 +1,58 @@
1
+ module Booth
2
+ module Userland
3
+ module Onboardings
4
+ module Transitions
5
+ module Update
6
+ class ChooseMode
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :onboarding
10
+
11
+ def self.applicable?(params:)
12
+ # `mode` sets to a specific mode and `reset` clears any mode.
13
+ params.key?(:mode) || params.key?(:reset)
14
+ end
15
+
16
+ def call
17
+ do_reset_password
18
+ .on_success { do_reset_otp }
19
+ .on_success { do_reset_webauth }
20
+ .on_success { do_update_mode }
21
+ end
22
+
23
+ private
24
+
25
+ def do_reset_password
26
+ ::Booth::Userland::Onboardings::Transitions::Update::ResetPassword.call(onboarding:,
27
+ request:)
28
+ end
29
+
30
+ def do_reset_otp
31
+ ::Booth::Userland::Onboardings::Transitions::Update::ResetOtp.call(onboarding:,
32
+ request:)
33
+ end
34
+
35
+ def do_reset_webauth
36
+ ::Booth::Userland::Onboardings::Transitions::Update::ResetWebauth.call(onboarding:,
37
+ request:)
38
+ end
39
+
40
+ def do_update_mode
41
+ if onboarding.update mode: mode_param
42
+ debug { "You chose the mode #{@onboarding.mode.inspect}" }
43
+ return Tron.success :mode_chosen
44
+ end
45
+
46
+ debug { "Could not choose mode #{mode_param.inspect}: #{onboarding.errors.to_a.to_sentence}" }
47
+ Tron.failure :mode_update_failed
48
+ end
49
+
50
+ def mode_param
51
+ params[:mode] || 'first_time'
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,41 @@
1
+ module Booth
2
+ module Userland
3
+ module Onboardings
4
+ module Transitions
5
+ module Update
6
+ class ChoosePassword
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :onboarding
10
+
11
+ def self.applicable?(params:)
12
+ params.key?(:onboarding) && params[:onboarding].key?(:password)
13
+ end
14
+
15
+ def call
16
+ do_update_password
17
+ end
18
+
19
+ private
20
+
21
+ def do_update_password
22
+ if onboarding.update password: password_param,
23
+ password_chosen_at: Time.current
24
+ debug { 'You chose a password' }
25
+ return Tron.success :password_chosen
26
+ end
27
+
28
+ public_message = onboarding.errors.to_a.to_sentence
29
+ debug { "Could not choose password: #{public_message}" }
30
+ Tron.failure :password_update_failed, public_message:
31
+ end
32
+
33
+ def password_param
34
+ params.require(:onboarding).permit(:password)[:password]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,50 @@
1
+ module Booth
2
+ module Userland
3
+ module Onboardings
4
+ module Transitions
5
+ module Update
6
+ class ChooseWebauthNickname
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :onboarding
10
+
11
+ def self.applicable?(params:)
12
+ params&.key?(:onboarding) && params[:onboarding]&.key?(:authenticator_nickname)
13
+ end
14
+
15
+ def call
16
+ do_check_blank_nickname
17
+ .on_success { do_update_webauth }
18
+ end
19
+
20
+ private
21
+
22
+ def do_check_blank_nickname
23
+ return Tron.success :nickname_is_present if nickname_param.present?
24
+
25
+ onboarding.errors.add(:authenticator_nickname, :blank)
26
+ public_message = onboarding.errors.to_a.to_sentence
27
+ debug { 'The nickname was blank' }
28
+ Tron.failure :nickname_is_blank, public_message:
29
+ end
30
+
31
+ def do_update_webauth
32
+ if onboarding.update authenticator_nickname: nickname_param
33
+ debug { 'The nickname successfully changed' }
34
+ Tron.success :webauth_nickname_saved
35
+ else
36
+ public_message = onboarding.errors.to_a.to_sentence
37
+ debug { "The nickname could not be updated: #{public_message}" }
38
+ Tron.failure :webauth_nickname_failed, public_message:
39
+ end
40
+ end
41
+
42
+ def nickname_param
43
+ params.require(:onboarding).permit(:authenticator_nickname)[:authenticator_nickname]
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,58 @@
1
+ module Booth
2
+ module Userland
3
+ module Onboardings
4
+ module Transitions
5
+ module Update
6
+ class ConfirmOtp
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :onboarding
10
+
11
+ def self.applicable?(params:)
12
+ params.key?(:onboarding) && params[:onboarding].key?(:otp_confirmation)
13
+ end
14
+
15
+ def call
16
+ do_check_otp_code_syntax
17
+ .on_success { do_authenticate_otp }
18
+ .on_success { do_update_confirmation }
19
+ end
20
+
21
+ private
22
+
23
+ def do_check_otp_code_syntax
24
+ check = ::Booth::Syntaxes::Otp.call(code_param)
25
+ return check if check.failure?
26
+
27
+ @normalized_otp_code = check.normalized_otp
28
+ check
29
+ end
30
+
31
+ def do_authenticate_otp
32
+ return Tron.success :correct_code if @onboarding.authenticate_otp(@normalized_otp_code)
33
+
34
+ @onboarding.errors.add :base, I18n.t('booth.wrong_otp')
35
+ public_message = onboarding.errors.to_a.to_sentence
36
+ debug { "Could not confirm otp: #{public_message}" }
37
+ Tron.failure :otp_did_not_match, public_message:
38
+ end
39
+
40
+ def do_update_confirmation
41
+ if @onboarding.update(otp_confirmed_at: Time.current)
42
+ debug { 'You confirmed your otp' }
43
+ return Tron.success :otp_confirmed
44
+ end
45
+
46
+ debug { "The confirmation was correct but the update failed: #{onboarding.errors.to_a.to_sentence}" }
47
+ Tron.failure :correct_otp_confirmation_failed
48
+ end
49
+
50
+ def code_param
51
+ params.require(:onboarding).permit(:otp_confirmation)[:otp_confirmation]
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,49 @@
1
+ module Booth
2
+ module Userland
3
+ module Onboardings
4
+ module Transitions
5
+ module Update
6
+ class ConfirmPassword
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :onboarding
10
+
11
+ def self.applicable?(params:)
12
+ params.key?(:onboarding) && params[:onboarding].key?(:password_confirmation)
13
+ end
14
+
15
+ def call
16
+ do_authenticate_password
17
+ .on_success { do_update_confirmation }
18
+ end
19
+
20
+ private
21
+
22
+ def do_authenticate_password
23
+ return Tron.success :password_matches if @onboarding.authenticate_password(password)
24
+
25
+ @onboarding.errors.add :password_confirmation, :confirmation
26
+ public_message = onboarding.errors.to_a.to_sentence
27
+ debug { "Could not confirm password: #{public_message}" }
28
+ Tron.failure :password_did_not_match, public_message:
29
+ end
30
+
31
+ def do_update_confirmation
32
+ if @onboarding.update(password_confirmed_at: Time.current)
33
+ debug { 'You confirmed your password' }
34
+ return Tron.success :password_confirmed
35
+ end
36
+
37
+ debug { "The confirmation was correct but the update failed: #{onboarding.errors.to_a.to_sentence}" }
38
+ Tron.failure :correct_password_confirmation_failed
39
+ end
40
+
41
+ def password
42
+ params.require(:onboarding).permit(:password_confirmation)[:password_confirmation]
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,31 @@
1
+ module Booth
2
+ module Userland
3
+ module Onboardings
4
+ module Transitions
5
+ module Update
6
+ class RegisterOtp
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :onboarding
10
+
11
+ def self.applicable?(params:)
12
+ params&.key?(:register_otp) && !params&.key?(:onboarding)
13
+ end
14
+
15
+ def call
16
+ do_register
17
+ end
18
+
19
+ private
20
+
21
+ def do_register
22
+ return Tron.success :otp_secret_regenerated if onboarding.update(otp_registered_at: Time.current)
23
+
24
+ Tron.failure :generating_otp_secret_failed
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,40 @@
1
+ module Booth
2
+ module Userland
3
+ module Onboardings
4
+ module Transitions
5
+ module Update
6
+ class ResetOtp
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :onboarding
10
+
11
+ def self.applicable?(params:)
12
+ params.key?(:reset_otp)
13
+ end
14
+
15
+ def call
16
+ do_regenerate_secret
17
+ .on_success { do_reset_confirmation }
18
+ end
19
+
20
+ def do_regenerate_secret
21
+ return Tron.success :secret_regenerated if onboarding.otp_regenerate_secret
22
+
23
+ Tron.failure :secret_regeneration_failed
24
+ end
25
+
26
+ def do_reset_confirmation
27
+ if onboarding.update(otp_registered_at: nil, otp_confirmed_at: nil)
28
+ debug { 'The Onboarding otp was reset.' }
29
+ return Tron.success :otp_reset
30
+ end
31
+
32
+ debug { "Could not reset otp: #{onboarding.errors.to_a.to_sentence}" }
33
+ Tron.failure :otp_reset_failed
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ module Booth
2
+ module Userland
3
+ module Onboardings
4
+ module Transitions
5
+ module Update
6
+ class ResetPassword
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :onboarding
10
+
11
+ def self.applicable?(params:)
12
+ params.key?(:reset_password)
13
+ end
14
+
15
+ def call
16
+ do_reset_password_confirmation
17
+ end
18
+
19
+ private
20
+
21
+ def do_reset_password_confirmation
22
+ if onboarding.update(password_chosen_at: nil, password_confirmed_at: nil)
23
+ debug { 'The Onboarding password was reset.' }
24
+ return Tron.success :password_reset
25
+ end
26
+
27
+ debug { "Could not reset password: #{onboarding.errors.to_a.to_sentence}" }
28
+ Tron.failure :password_reset_failed
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,46 @@
1
+ module Booth
2
+ module Userland
3
+ module Onboardings
4
+ module Transitions
5
+ module Update
6
+ class ResetWebauth
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :onboarding
10
+
11
+ def self.applicable?(params:)
12
+ params.key?(:reset_webauth)
13
+ end
14
+
15
+ def call
16
+ do_clear_webauth
17
+ end
18
+
19
+ private
20
+
21
+ def do_clear_webauth
22
+ if onboarding.update(niled_attributes)
23
+ debug { 'The Onboarding webauth was reset.' }
24
+ return Tron.success :webauth_reset
25
+ end
26
+
27
+ debug { "Could not reset webauth: #{onboarding.errors.to_a.to_sentence}" }
28
+ Tron.failure :webauth_reset_failed
29
+ end
30
+
31
+ def niled_attributes
32
+ {
33
+ webauthn_id: nil,
34
+ authenticator_id: nil,
35
+ authenticator_nickname: nil,
36
+ authenticator_challenge: nil,
37
+ authenticator_public_key: nil,
38
+ authenticator_sign_count: nil
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,40 @@
1
+ module Booth
2
+ module Userland
3
+ module Onboardings
4
+ module Transitions
5
+ module Update
6
+ class WebauthAuthenticationInitiation
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :onboarding
10
+
11
+ def self.applicable?(params:)
12
+ params&.key?(:test_webauth) && !params&.key?(:onboarding)
13
+ end
14
+
15
+ def call
16
+ do_challenge
17
+ end
18
+
19
+ private
20
+
21
+ def do_challenge
22
+ webauth = ::Booth::Webauth::OptionsForGet.call(
23
+ allowed_device_ids: onboarding.authenticator_id,
24
+ requires_user_verification: onboarding.requires_user_verification?
25
+ )
26
+
27
+ debug { "Remembering test challenge #{webauth.challenge.inspect}" }
28
+
29
+ if onboarding.update authenticator_challenge: webauth.challenge
30
+ Tron.success :test_challenge_created, public_json: webauth.as_json, http_status: :created
31
+ else
32
+ Tron.failure :storing_test_challenge_failed
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,59 @@
1
+ module Booth
2
+ module Userland
3
+ module Onboardings
4
+ module Transitions
5
+ module Update
6
+ class WebauthAuthenticationVerification
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :onboarding
10
+
11
+ def self.applicable?(params:)
12
+ params&.key?(:test_webauth) && params[:onboarding]&.key?(:rawId)
13
+ end
14
+
15
+ def call
16
+ do_verify_response
17
+ .on_success { do_persist_confirmation }
18
+ end
19
+
20
+ private
21
+
22
+ def do_verify_response
23
+ debug { 'Verifying challenge...' }
24
+ # TODO: Replate with ::Booth::Webauth::AuthenticationVerification (?)
25
+ webauth.verify(@onboarding.authenticator_challenge,
26
+ public_key: @onboarding.authenticator_public_key,
27
+ sign_count: @onboarding.authenticator_sign_count)
28
+
29
+ Tron.success :challenge_response_correct
30
+ rescue WebAuthn::Error => e
31
+ debug { "Webauth Handshake failed: #{e.message}" }
32
+ Tron.failure :invalid_challenge_response
33
+ end
34
+
35
+ def do_persist_confirmation
36
+ debug { 'Updating succeeded sign count and authenticator confirmation...' }
37
+ if onboarding.update(authenticator_sign_count: webauth.sign_count,
38
+ authenticator_confirmed_at: Time.current)
39
+
40
+ return Tron.success :webauth_authentication_verification_successful, public_json: {},
41
+ http_status: :created
42
+ end
43
+
44
+ Tron.failure :storing_response_failed
45
+ end
46
+
47
+ def webauth
48
+ @webauth ||= ::WebAuthn::Credential.from_get(onboarding_params)
49
+ end
50
+
51
+ def onboarding_params
52
+ params.require(:onboarding).permit!
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,46 @@
1
+ module Booth
2
+ module Userland
3
+ module Onboardings
4
+ module Transitions
5
+ module Update
6
+ class WebauthRegistrationInitiation
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :onboarding
10
+
11
+ def self.applicable?(params:)
12
+ params&.key?(:register_webauth) && !params&.key?(:onboarding)
13
+ end
14
+
15
+ def call
16
+ do_challenge
17
+ end
18
+
19
+ private
20
+
21
+ def do_challenge
22
+ debug { "Remembering registration challenge #{webauth.challenge.inspect}" }
23
+
24
+ if onboarding.update authenticator_challenge: webauth.challenge
25
+ return Tron.success :registration_challenge_created, public_json: webauth.as_json,
26
+ http_status: :created
27
+ end
28
+
29
+ Tron.failure :storing_registration_challenge_failed
30
+ end
31
+
32
+ def webauth
33
+ @webauth ||= ::Booth::Webauth::OptionsForCreate.call(
34
+ webauthn_id: onboarding.webauthn_id,
35
+ username: onboarding.credential.username,
36
+ requires_user_verification: onboarding.requires_user_verification?
37
+ # No need to exclude existing hardware keys.
38
+ # You can not register more than one hardware key in this onboarding.
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,56 @@
1
+ module Booth
2
+ module Userland
3
+ module Onboardings
4
+ module Transitions
5
+ module Update
6
+ class WebauthRegistrationVerification
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :onboarding
10
+
11
+ def self.applicable?(params:)
12
+ params&.key?(:register_webauth) && params[:onboarding]&.key?(:rawId)
13
+ end
14
+
15
+ def call
16
+ do_verify_response
17
+ .on_success { do_persist_keys }
18
+ end
19
+
20
+ private
21
+
22
+ def do_verify_response
23
+ debug { "Verifying challenge #{@onboarding.authenticator_challenge.inspect}" }
24
+ webauth.verify(@onboarding.authenticator_challenge)
25
+
26
+ Tron.success :challenge_response_correct
27
+ rescue WebAuthn::Error => e
28
+ debug { "Webauth Handshake failed: #{e.message}" }
29
+ Tron.failure :invalid_challenge_response
30
+ end
31
+
32
+ def do_persist_keys
33
+ debug { "Persisting authenticator information, the sign count is now at #{webauth.sign_count}..." }
34
+ if onboarding.update(authenticator_id: ::Base64.strict_encode64(webauth.raw_id),
35
+ authenticator_public_key: webauth.public_key,
36
+ authenticator_sign_count: webauth.sign_count)
37
+
38
+ return Tron.success :challenge_accepted, public_json: {}, http_status: :created
39
+ end
40
+
41
+ Tron.failure :storing_response_failed
42
+ end
43
+
44
+ def webauth
45
+ @webauth ||= ::WebAuthn::Credential.from_create(onboarding_params)
46
+ end
47
+
48
+ def onboarding_params
49
+ params.require(:onboarding).permit!
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,68 @@
1
+ module Booth
2
+ module Userland
3
+ module Onboardings
4
+ class Update
5
+ include ::Booth::Concerns::Action
6
+
7
+ def call
8
+ request.must_be_patch!
9
+
10
+ do_find_onboarding
11
+ .on_success { do_check_propagation }
12
+ .on_success { do_transition }
13
+ end
14
+
15
+ private
16
+
17
+ def do_find_onboarding
18
+ finding = ::Booth::Onboardings::Find.call(secret_key: params[:id])
19
+ return finding if finding.failure?
20
+
21
+ @onboarding = finding.onboarding
22
+ finding
23
+ end
24
+
25
+ def do_check_propagation
26
+ return Tron.success :not_yet_propagated unless @onboarding.propagated?
27
+
28
+ Tron.failure :already_propagated
29
+ end
30
+
31
+ def initialize_transition
32
+ transition.call(onboarding: @onboarding, request:)
33
+ end
34
+
35
+ def after_transition
36
+ @onboarding.reload # Ensure the onboarding instance is in an invalid state after a failed update attempt.
37
+ debug { "Onboarding updated, now in step #{@onboarding.step}" }
38
+ return unless @onboarding.completed?
39
+
40
+ ::Booth::Onboardings::PropagateToCredential.call(@onboarding, ip: request.ip, agent: request.agent)
41
+ # The `CreateRegistration` flow may already have logged you in.
42
+ return if request.authentication.logged_in?
43
+
44
+ debug { 'Nobody is logged in, so this Onboarding was without prior self-registration. Logging you in now...'}
45
+ ::Booth::Sessions::CreateAndLogin.call(credential: @onboarding.credential, request:)
46
+ end
47
+
48
+ def transitions # rubocop:disable Metrics/MethodLength
49
+ [
50
+ ::Booth::Userland::Onboardings::Transitions::Update::ChooseMode,
51
+ ::Booth::Userland::Onboardings::Transitions::Update::ChoosePassword,
52
+ ::Booth::Userland::Onboardings::Transitions::Update::ChooseWebauthNickname,
53
+ ::Booth::Userland::Onboardings::Transitions::Update::ConfirmOtp,
54
+ ::Booth::Userland::Onboardings::Transitions::Update::ConfirmPassword,
55
+ ::Booth::Userland::Onboardings::Transitions::Update::RegisterOtp,
56
+ ::Booth::Userland::Onboardings::Transitions::Update::ResetOtp,
57
+ ::Booth::Userland::Onboardings::Transitions::Update::ResetPassword,
58
+ ::Booth::Userland::Onboardings::Transitions::Update::ResetWebauth,
59
+ ::Booth::Userland::Onboardings::Transitions::Update::WebauthAuthenticationInitiation,
60
+ ::Booth::Userland::Onboardings::Transitions::Update::WebauthAuthenticationVerification,
61
+ ::Booth::Userland::Onboardings::Transitions::Update::WebauthRegistrationInitiation,
62
+ ::Booth::Userland::Onboardings::Transitions::Update::WebauthRegistrationVerification,
63
+ ]
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end