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,48 @@
1
+ module Booth
2
+ module Userland
3
+ module PasswordResets
4
+ module Transitions
5
+ module Update
6
+ class ChoosePassword
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :password_reset
10
+
11
+ def self.applicable?(params:)
12
+ params&.key?(:password_reset) && params[:password_reset]&.key?(:password)
13
+ end
14
+
15
+ def call
16
+ do_check_password_reset
17
+ .on_success { do_update_password }
18
+ end
19
+
20
+ private
21
+
22
+ def do_check_password_reset
23
+ return Tron.failure :missing_password_reset unless password_reset.is_a?(::Booth::Models::PasswordReset)
24
+
25
+ Tron.success :password_reset_exists
26
+ end
27
+
28
+ def do_update_password
29
+ if password_reset.update password: password,
30
+ password_chosen_at: Time.current
31
+ debug { 'You chose a password' }
32
+ Tron.success :password_chosen
33
+ else
34
+ public_message = password_reset.errors.to_a.to_sentence
35
+ debug { "Could not choose password: #{public_message}" }
36
+ Tron.failure :password_update_failed, public_message:
37
+ end
38
+ end
39
+
40
+ def password
41
+ params&.require(:password_reset)&.permit(:password)&.[](:password)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,54 @@
1
+ module Booth
2
+ module Userland
3
+ module PasswordResets
4
+ module Transitions
5
+ module Update
6
+ class ConfirmPassword
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :password_reset
10
+
11
+ def self.applicable?(params:)
12
+ params.key?(:password_reset) && params[:password_reset].key?(:password_confirmation)
13
+ end
14
+
15
+ def call
16
+ do_compare_password
17
+ .on_success { do_update_password }
18
+ end
19
+
20
+ private
21
+
22
+ def do_compare_password
23
+ return Tron.success :passwords_match if @password_reset.authenticate_password(password_param)
24
+
25
+ @password_reset.errors.add :password_confirmation, :confirmation
26
+ public_message = password_reset.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_password
32
+ if @password_reset.update(password_confirmed_at: Time.current, consumer_ip: request.ip)
33
+ debug { 'You confirmed your password' }
34
+ ::Booth::PasswordResets::PropagateToCredential.call(@password_reset)
35
+ Tron.success :password_confirmed,
36
+ public_message: I18n.t('booth.password_successfully_reset')
37
+
38
+ else
39
+ debug do
40
+ "The confirmation was correct but the update failed: #{password_reset.errors.to_a.to_sentence}"
41
+ end
42
+ Tron.failure :correct_password_confirmation_failed
43
+ end
44
+ end
45
+
46
+ def password_param
47
+ params.require(:password_reset).permit(:password_confirmation)[:password_confirmation]
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,29 @@
1
+ module Booth
2
+ module Userland
3
+ module PasswordResets
4
+ module Transitions
5
+ module Update
6
+ class ResetPassword
7
+ include ::Booth::Concerns::Transition
8
+
9
+ option :password_reset
10
+
11
+ def self.applicable?(params:)
12
+ params.key?(:reset_password)
13
+ end
14
+
15
+ def call
16
+ if password_reset.update password_chosen_at: nil, password_confirmed_at: nil
17
+ debug { 'The PasswordReset password was reset.' }
18
+ Tron.success :password_reset
19
+ else
20
+ debug { "Could not reset password: #{password_reset.errors.to_a.to_sentence}" }
21
+ Tron.failure :password_reset_failed
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,65 @@
1
+ module Booth
2
+ module Userland
3
+ module PasswordResets
4
+ class Update
5
+ include ::Booth::Concerns::Action
6
+
7
+ def call
8
+ request.must_be_patch!
9
+
10
+ do_find_password_reset
11
+ .on_success { do_check_scope }
12
+ .on_success { do_check_logged_out }
13
+ .on_success { do_transition }
14
+ end
15
+
16
+ private
17
+
18
+ def do_find_password_reset
19
+ finding = ::Booth::PasswordResets::Find.call(secret_key: params[:id])
20
+ return finding if finding.failure?
21
+
22
+ @password_reset = finding.password_reset
23
+ finding
24
+ end
25
+
26
+ def do_check_scope
27
+ ::Booth::Syntaxes::ScopeComparison.call this: scope, that: @password_reset.credential.scope
28
+ end
29
+
30
+ def do_check_logged_out
31
+ unless request.authentication.logged_in?
32
+ debug { "Good, nobody happens to be already logged in in scope #{scope}" }
33
+ return Tron.success :not_logged_in
34
+ end
35
+
36
+ debug { "When updating a new password, nobody should be logged in its scope #{scope.inspect}" }
37
+ Tron.failure :currently_logged_in
38
+ end
39
+
40
+ def after_transition
41
+ @password_reset.reload # Ensure the `password_reset` instance is in a valid state after a failed update
42
+ debug { "PasswordReset updated, now in status #{@password_reset.step}" }
43
+
44
+ # If (and only if!) this account is protected *only* by a password...
45
+ return unless @password_reset.completed? && @password_reset.credential.mode_username_and_password?
46
+
47
+ # ...then we log in right away.
48
+ ::Booth::Sessions::CreateAndLogin.call(credential: @password_reset.credential, request:)
49
+ end
50
+
51
+ def initialize_transition
52
+ transition.call(password_reset: @password_reset, request:)
53
+ end
54
+
55
+ def transitions
56
+ [
57
+ ::Booth::Userland::PasswordResets::Transitions::Update::ChoosePassword,
58
+ ::Booth::Userland::PasswordResets::Transitions::Update::ConfirmPassword,
59
+ ::Booth::Userland::PasswordResets::Transitions::Update::ResetPassword,
60
+ ]
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,41 @@
1
+ module Booth
2
+ module Userland
3
+ module Passwords
4
+ class Destroy
5
+ include ::Booth::Concerns::Action
6
+
7
+ def call
8
+ request.must_be_delete!
9
+ request.must_be_html!
10
+ request.must_be_logged_in!
11
+
12
+ ::Booth::Userland::Passwords::Guards::Sudo.call(request:, credential:) { return _1 }
13
+
14
+ do_require_eligible_credential
15
+ .on_success { do_destroy }
16
+ end
17
+
18
+ private
19
+
20
+ def do_require_eligible_credential
21
+ return Tron.success :can_remove_password if ::Booth::Credentials::Modes::PasswordRemovable.call(credential)
22
+
23
+ Tron.failure :credential_not_passwordable, public_message: I18n.t('booth.password_not_eligible')
24
+ end
25
+
26
+ def do_destroy
27
+ if credential.mode_username_and_webauth!
28
+ debug { 'Password has been removed from this credential' }
29
+ return Tron.success :password_removed, public_message: I18n.t('booth.password_removed')
30
+ end
31
+
32
+ Tron.failure :password_removal_failed
33
+ end
34
+
35
+ def credential
36
+ @credential ||= ::Booth::Models::Credential.find(request.authentication.credential_id)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,54 @@
1
+ module Booth
2
+ module Userland
3
+ module Passwords
4
+ class Edit
5
+ include ::Booth::Concerns::Action
6
+
7
+ def call
8
+ request.must_be_get!
9
+ request.must_be_html!
10
+ request.must_be_logged_in!
11
+
12
+ ::Booth::Userland::Passwords::Guards::Manageable.call(credential:) { return _1 }
13
+ ::Booth::Userland::Passwords::Guards::Sudo.call(request:, credential:) { return _1 }
14
+
15
+ do_clear_timed_out_sudo
16
+ .on_success { do_edit_password }
17
+ end
18
+
19
+ private
20
+
21
+ delegate :sudo, :authentication, to: :request
22
+
23
+ def do_clear_timed_out_sudo
24
+ return Tron.success :sudo_still_granted if sudo.password?
25
+
26
+ # If you had chosen a new password and your sudo ran out,
27
+ # then we should also clear the chosen password again. Simply start all over.
28
+ storage.password_digest = nil
29
+ Tron.success :cleared_password_digest
30
+ end
31
+
32
+ def do_edit_password
33
+ Tron.success :editing_password, step:
34
+ end
35
+
36
+ def step
37
+ return :successfully_changed if storage.password_recently_changed?
38
+ return :enter_old_password unless sudo.password?
39
+ return :choose_new_password if storage.password_digest.blank?
40
+
41
+ :confirm_new_password
42
+ end
43
+
44
+ def storage
45
+ request.storage.password
46
+ end
47
+
48
+ def credential
49
+ @credential ||= ::Booth::Models::Credential.find(request.authentication.credential_id)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,21 @@
1
+ module Booth
2
+ module Userland
3
+ module Passwords
4
+ module Guards
5
+ class Manageable
6
+ include ::Booth::Logging
7
+ include ::Booth::MethodObject
8
+
9
+ option :credential
10
+
11
+ def call
12
+ return if ::Booth::Credentials::Modes::PasswordManageable.call(credential)
13
+
14
+ debug { 'Password is not relevant to this credential' }
15
+ yield Tron.failure :password_not_configurable, public_message: I18n.t('booth.password_unavailable')
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Booth
2
+ module Userland
3
+ module Passwords
4
+ module Guards
5
+ class Removable
6
+ include ::Booth::Logging
7
+ include ::Booth::MethodObject
8
+
9
+ option :credential
10
+
11
+ def call
12
+ return if ::Booth::Credentials::Modes::PasswordRemovable.call(credential)
13
+
14
+ debug { 'Password is not removable from this credential' }
15
+ yield Tron.failure :password_not_removable, public_message: I18n.t('booth.password_unavailable')
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Booth
2
+ module Userland
3
+ module Passwords
4
+ module Guards
5
+ class Sudo
6
+ include ::Booth::MethodObject
7
+
8
+ option :request
9
+ option :credential
10
+
11
+ def call
12
+ return if credential.mode_first_time?
13
+ return if credential.mode_username_and_webauth?
14
+
15
+ request.sudo.guard_with_password { yield _1 }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ module Booth
2
+ module Userland
3
+ module Passwords
4
+ class Remove
5
+ include ::Booth::Concerns::Action
6
+
7
+ def call
8
+ request.must_be_get!
9
+ request.must_be_html!
10
+ request.must_be_logged_in!
11
+
12
+ ::Booth::Userland::Passwords::Guards::Removable.call(credential:) { return _1 }
13
+ ::Booth::Userland::Passwords::Guards::Sudo.call(request:, credential:) { return _1 }
14
+
15
+ do_render
16
+ end
17
+
18
+ private
19
+
20
+ def do_render
21
+ Tron.success :todo, step:
22
+ end
23
+
24
+ def step
25
+ ::Booth::Userland::Passwords::Transitions::Remove::Step.call(credential:)
26
+ end
27
+
28
+ def credential
29
+ @credential ||= ::Booth::Models::Credential.find(request.authentication.credential_id)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ module Booth
2
+ module Userland
3
+ module Passwords
4
+ class Show
5
+ include ::Booth::Concerns::Action
6
+
7
+ def call
8
+ request.must_be_get!
9
+ request.must_be_html!
10
+ request.must_be_logged_in!
11
+
12
+ ::Booth::Userland::Passwords::Guards::Manageable.call(credential:) { return _1 }
13
+ ::Booth::Userland::Passwords::Guards::Sudo.call(request:, credential:) { return _1 }
14
+
15
+ do_show
16
+ end
17
+
18
+ private
19
+
20
+ def do_show
21
+ return Tron.success(:otp_addable, step: :add) if credential.mode_username_and_webauth?
22
+
23
+ Tron.success :manage_password, step: :show
24
+ end
25
+
26
+ def credential
27
+ @credential ||= ::Booth::Models::Credential.find(request.authentication.credential_id)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,55 @@
1
+ module Booth
2
+ module Userland
3
+ module Passwords
4
+ class Sudo
5
+ include ::Booth::Concerns::Action
6
+
7
+ def call
8
+ request.must_be_post!
9
+ request.must_be_html!
10
+ request.must_be_logged_in!
11
+
12
+ do_find_credential
13
+ .on_success { do_require_credential_has_password }
14
+ .on_success { do_check_password }
15
+ .on_success { do_grant_sudo }
16
+ end
17
+
18
+ private
19
+
20
+ def do_find_credential
21
+ @credential = ::Booth::Models::Credential.find(request.authentication.credential_id)
22
+ return Tron.success :found_credential if @credential
23
+
24
+ Tron.failure :missing_credential
25
+ end
26
+
27
+ def do_require_credential_has_password
28
+ return Tron.success :credential_has_only_password if @credential.mode_username_and_password?
29
+ return Tron.success :credential_has_password_and_otp if @credential.mode_username_password_and_otp?
30
+ return Tron.success :credential_has_password_and_webauth if @credential.mode_username_password_and_webauth?
31
+
32
+ Tron.failure :credential_has_no_password, public_message: I18n.t('booth.password_not_configured')
33
+ end
34
+
35
+ def do_check_password
36
+ ::Booth::Credentials::PasswordAuthentication.call(
37
+ credential: @credential,
38
+ password: password_param,
39
+ ip: request.ip,
40
+ agent: request.agent
41
+ )
42
+ end
43
+
44
+ def do_grant_sudo
45
+ request.sudo.password!
46
+ Tron.success :password_sudo_granted
47
+ end
48
+
49
+ def password_param
50
+ params.require(:password).permit(:password)[:password]
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,27 @@
1
+ module Booth
2
+ module Userland
3
+ module Passwords
4
+ module Transitions
5
+ module Remove
6
+ class Step
7
+ include ::Booth::MethodObject
8
+
9
+ option :credential
10
+
11
+ def call
12
+ return :add_authenticator if authenticators.none?(&:supports_user_verification)
13
+
14
+ :remove
15
+ end
16
+
17
+ private
18
+
19
+ def authenticators
20
+ credential.authenticators.registered_scope
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,62 @@
1
+ module Booth
2
+ module Userland
3
+ module Passwords
4
+ module Transitions
5
+ module Update
6
+ class ChoosePassword
7
+ include ::Booth::Concerns::Transition
8
+
9
+ def self.applicable?(params:)
10
+ params.dig(:password, :new_password)
11
+ end
12
+
13
+ def call
14
+ do_check_sudo
15
+ .on_success { do_check_password }
16
+ .on_success { do_remember_password }
17
+ end
18
+
19
+ private
20
+
21
+ def do_check_sudo
22
+ return Tron.success :still_has_sudo if sudo.password?
23
+
24
+ Tron.failure :missing_sudo, public_message: I18n.t('booth.password_change_timeout',
25
+ lifespan_minutes: (sudo.lifespan / 60))
26
+ end
27
+
28
+ def do_check_password
29
+ @dummy_credential = ::Booth::Models::Credential.new(
30
+ password: password_param,
31
+ username: :dummy,
32
+ allowed_modes: [:first_time]
33
+ )
34
+ return Tron.success :password_is_acceptable if @dummy_credential.valid?
35
+
36
+ debug { 'The newly chosen password is not acceptable' }
37
+ Tron.failure :invalid_password, public_message: @dummy_credential.errors.full_messages.to_sentence
38
+ end
39
+
40
+ def do_remember_password
41
+ storage.password_digest = @dummy_credential.password_digest
42
+
43
+ Tron.success :remembered_password
44
+ end
45
+
46
+ def password_param
47
+ params.require(:password).permit(:new_password)[:new_password]
48
+ end
49
+
50
+ def storage
51
+ request.storage.password
52
+ end
53
+
54
+ def sudo
55
+ request.sudo
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,82 @@
1
+ module Booth
2
+ module Userland
3
+ module Passwords
4
+ module Transitions
5
+ module Update
6
+ class ConfirmPassword
7
+ include ::Booth::Concerns::Transition
8
+
9
+ def self.applicable?(params:)
10
+ params.dig(:password, :password)
11
+ end
12
+
13
+ def call
14
+ do_check_sudo
15
+ .on_success { do_compare_password }
16
+ .on_success { do_update_credential }
17
+ end
18
+
19
+ private
20
+
21
+ def do_check_sudo
22
+ return Tron.success :still_has_sudo if sudo.password?
23
+
24
+ Tron.failure :missing_sudo, public_message: I18n.t('booth.password_change_timeout',
25
+ lifespan_minutes: (sudo.lifespan / 60))
26
+ end
27
+
28
+ def do_compare_password
29
+ @dummy_credential = ::Booth::Models::Credential.new(
30
+ password_digest: storage.password_digest,
31
+ password_confirmation: password_param,
32
+ username: :dummy,
33
+ allowed_modes: [:first_time]
34
+ )
35
+
36
+ if @dummy_credential.valid?
37
+ debug { 'The password confirmation was typed in correctly' }
38
+ return Tron.success :passwords_match
39
+ end
40
+
41
+ debug { 'The password confirmation did not match' }
42
+ Tron.failure :passwords_dont_match, public_message: @dummy_credential.errors.full_messages.to_sentence
43
+ end
44
+
45
+ def do_update_credential
46
+ credential = ::Booth::Models::Credential.find(authentication.credential_id)
47
+
48
+ if credential.update(password: password_param)
49
+ debug { "Successfully updated password of credential with ID #{credential.id}" }
50
+ storage.reset
51
+ storage.password_recently_changed!
52
+
53
+ # Prolong sudo to give the user more time to revoke all other sessions.
54
+ sudo.password!
55
+ return Tron.success :password_updated
56
+ end
57
+
58
+ debug { "Could not persist new password for credential with ID #{credential.id}" }
59
+ Tron.failure :persistence_failed, public_message: credential.errors.full_messages.to_sentence
60
+ end
61
+
62
+ def password_param
63
+ params.require(:password).permit(:password)[:password]
64
+ end
65
+
66
+ def storage
67
+ request.storage.password
68
+ end
69
+
70
+ def authentication
71
+ request.authentication
72
+ end
73
+
74
+ def sudo
75
+ request.sudo
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,33 @@
1
+ module Booth
2
+ module Userland
3
+ module Passwords
4
+ class Update
5
+ include ::Booth::Concerns::Action
6
+
7
+ def call
8
+ request.must_be_patch!
9
+ request.must_be_logged_in!
10
+
11
+ ::Booth::Userland::Passwords::Guards::Manageable.call(credential:) { return _1 }
12
+ ::Booth::Userland::Passwords::Guards::Sudo.call(request:, credential:) { return _1 }
13
+
14
+ do_transition
15
+ end
16
+
17
+ private
18
+
19
+ def credential
20
+ @credential ||= ::Booth::Models::Credential.find(request.authentication.credential_id)
21
+ end
22
+
23
+ def transitions
24
+ [
25
+ ::Booth::Userland::Passwords::Transitions::Update::ChoosePassword,
26
+ # TODO: Add ResetPassword so that you can change your mind when confirming
27
+ ::Booth::Userland::Passwords::Transitions::Update::ConfirmPassword,
28
+ ]
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end