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,60 @@
1
+ module Booth
2
+ module Userland
3
+ module PersonalContests
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
+ debug { 'You want to receive your personal contest...' }
13
+
14
+ if contest.failure?
15
+ debug { "This credential doesn't have any contest right now." }
16
+ return Tron.success :not_contested, step: :not_contested
17
+ end
18
+
19
+ if contest.recently_responded
20
+ debug { 'The current contest has already been responded to' }
21
+ return Tron.success :contest_responded, step: :contest_solved
22
+ end
23
+
24
+ step = case contest.reason
25
+ when :login then :remote_login
26
+ when :support then :support_authentication
27
+ else raise "Unknown contest reason: #{contest.reason}"
28
+ end
29
+
30
+ debug { 'You have a contest to respond to' }
31
+ Tron.success :you_are_contested,
32
+ ip: contest.ip,
33
+ agent: contest.agent.presence,
34
+ location: contest.location.presence,
35
+ browser_name: contest.browser_name,
36
+ platform_name: contest.platform_name,
37
+ browser_image_path: contest.browser_image_path,
38
+ platform_image_path: contest.platform_image_path,
39
+ step:
40
+ end
41
+
42
+ private
43
+
44
+ def credential
45
+ return @credential if defined?(@credential)
46
+
47
+ id = request.authentication.credential_id
48
+ @credential = ::Booth::Models::Credential.find_by(id:)
49
+ end
50
+
51
+ def contest
52
+ return unless credential
53
+ return @contest if defined?(@contest)
54
+
55
+ @contest = ::Booth::Contests::Get.call(credential_id: credential.id)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,37 @@
1
+ module Booth
2
+ module Userland
3
+ module PersonalContests
4
+ class Update
5
+ include ::Booth::Concerns::Action
6
+
7
+ def call
8
+ request.must_be_patch!
9
+
10
+ do_check_logged_in
11
+ .on_success { do_respond }
12
+ end
13
+
14
+ def do_check_logged_in
15
+ return Tron.success :logged_in if credential
16
+
17
+ Tron.failure :not_logged_in
18
+ end
19
+
20
+ def do_respond
21
+ ::Booth::Contests::Respond.call(scope:, contest: credential.contest, request:)
22
+ end
23
+
24
+ private
25
+
26
+ delegate :contest, to: :credential, private: true
27
+
28
+ def credential
29
+ return @credential if defined?(@credential)
30
+
31
+ credential_id = request.authentication.credential_id
32
+ @credential = ::Booth::Models::Credential.find_by(id: credential_id)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,48 @@
1
+ module Booth
2
+ module Userland
3
+ module Recoveries
4
+ class Create
5
+ include ::Booth::Concerns::Action
6
+
7
+ def call
8
+ request.must_be_post!
9
+
10
+ do_remember_email
11
+ .on_success { do_create_password_reset }
12
+ end
13
+
14
+ private
15
+
16
+ delegate :username, to: :storage, private: true
17
+
18
+ def do_remember_email
19
+ storage.email = ::Booth::Syntaxes::Email.call(email_param).normalized_invalid_email
20
+
21
+ Tron.success :remembered_email
22
+ end
23
+
24
+ def do_create_password_reset
25
+ creation = ::Booth::Recoveries::Create.call(
26
+ scope:,
27
+ email: email_param,
28
+ ip: request.ip
29
+ )
30
+
31
+ creation.on_success do
32
+ storage.email = nil
33
+ end
34
+
35
+ creation
36
+ end
37
+
38
+ def storage
39
+ request.storage.recovery
40
+ end
41
+
42
+ def email_param
43
+ params.require(:recovery).permit(:email)[:email]
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,35 @@
1
+ module Booth
2
+ module Userland
3
+ module Recoveries
4
+ class New
5
+ include ::Booth::Concerns::Action
6
+
7
+ def call
8
+ request.must_be_get!
9
+ request.must_be_html!
10
+
11
+ if already_logged_in?
12
+ call_already_logged_in
13
+ else
14
+ Tron.failure :enter_email, step: :enter_email, email: storage.email
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def call_already_logged_in
21
+ debug { "Looks like you're already logged in" }
22
+ Tron.success :logged_in, step: :already_logged_in
23
+ end
24
+
25
+ def already_logged_in?
26
+ request.authentication.logged_in?
27
+ end
28
+
29
+ def storage
30
+ request.storage.recovery
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,56 @@
1
+ module Booth
2
+ module Userland
3
+ module Registrations
4
+ class Create
5
+ include ::Booth::Concerns::Action
6
+
7
+ option :allowed_modes
8
+
9
+ def call
10
+ request.must_be_post!
11
+
12
+ debug { "You want to register the username #{username_param.inspect}, let's see if it is taken." }
13
+ finding = ::Booth::Credentials::FindByUsername.call(username: username_param)
14
+ registration_storage.username = finding.normalized_invalid_username
15
+
16
+ finding.on_success do
17
+ debug { 'That username is taken. You should try to login instead.' }
18
+ login_storage.credential_for_username = finding.credential
19
+ public_message = I18n.t('booth.username_already_exists')
20
+ return Tron.success :username_exists, public_message:
21
+ end
22
+
23
+ debug { 'That username is available.' }
24
+ return finding unless finding.failure == :credential_not_found
25
+
26
+ creation = ::Booth::Credentials::CreateWithOnboarding.call(
27
+ username: finding.normalized_username,
28
+ allowed_modes:,
29
+ scope:
30
+ )
31
+
32
+ creation.on_success do
33
+ ::Booth::Sessions::CreateAndLogin.call(credential: creation.credential, request:)
34
+ return Tron.success :username_is_available, public_message: 'You have registered an account.'
35
+ end
36
+
37
+ Tron.failure :registration_failed, error: creation.error
38
+ end
39
+
40
+ private
41
+
42
+ def username_param
43
+ params.require(:registration).permit(:username)[:username]
44
+ end
45
+
46
+ def login_storage
47
+ request.storage.login
48
+ end
49
+
50
+ def registration_storage
51
+ request.storage.registration
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,39 @@
1
+ module Booth
2
+ module Userland
3
+ module Registrations
4
+ class New
5
+ include ::Booth::Concerns::Action
6
+
7
+ def call
8
+ request.must_be_get!
9
+ request.must_be_html!
10
+
11
+ if already_logged_in?
12
+ call_already_logged_in
13
+ else
14
+ Tron.failure :choose_username, step: :choose_username, username: storage.username
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def call_already_logged_in
21
+ debug { "Looks like you're already logged in" }
22
+ Tron.success :logged_in, step: :already_logged_in
23
+ end
24
+
25
+ def already_logged_in?
26
+ request.authentication.logged_in?
27
+ end
28
+
29
+ def username
30
+ storage.username
31
+ end
32
+
33
+ def storage
34
+ request.storage.registration
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ module Booth
2
+ module Userland
3
+ module Sessions
4
+ class DestroyOneOrOther
5
+ include ::Booth::Concerns::Action
6
+
7
+ def call
8
+ request.must_be_delete!
9
+ request.must_be_logged_in!
10
+
11
+ do_transition
12
+ end
13
+
14
+ private
15
+
16
+ def transitions
17
+ if request.authentication.mode == :username_and_webauth
18
+ webauth_transitions
19
+ else
20
+ password_transitions
21
+ end
22
+ end
23
+
24
+ def webauth_transitions
25
+ [
26
+ ::Booth::Userland::Sessions::Transitions::Destroy::EnterWebauth,
27
+ ::Booth::Userland::Sessions::Transitions::Destroy::WebauthAuthenticationInitiation,
28
+ ::Booth::Userland::Sessions::Transitions::Destroy::WebauthAuthenticationVerification,
29
+ ]
30
+ end
31
+
32
+ def password_transitions
33
+ [
34
+ ::Booth::Userland::Sessions::Transitions::Destroy::EnterPassword,
35
+ ::Booth::Userland::Sessions::Transitions::Destroy::VerifyPassword,
36
+ ]
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ module Booth
2
+ module Userland
3
+ module Sessions
4
+ class Index
5
+ include ::Booth::Concerns::Action
6
+
7
+ def call
8
+ request.must_be_get!
9
+ request.must_be_logged_in!
10
+
11
+ do_fetch_sessions
12
+ end
13
+
14
+ private
15
+
16
+ def do_fetch_sessions
17
+ ::Booth::Sessions::Index.call credential_id: authentication.credential_id,
18
+ current_session_id: authentication.session_id
19
+ end
20
+
21
+ def authentication
22
+ request.authentication
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ module Booth
2
+ module Userland
3
+ module Sessions
4
+ # `DELETE /sessions/123` may present a page with a WebAuth authentication challenge.
5
+ # That challenge is responded to asynchronously and in that response the server destroys the session.
6
+ # After that, the page is reloaded by JS using `GET /sessions/123`, which is the `show` action.
7
+ # But we don't actually have a show action for sessions. We just informatively redirect to the index action.
8
+ class Show
9
+ include ::Booth::Concerns::Action
10
+
11
+ def call
12
+ request.must_be_get!
13
+ request.must_be_html!
14
+ request.must_be_logged_in!
15
+
16
+ unless request.authentication.mode == :username_and_webauth
17
+ return Tron.failure :only_applicable_when_passwordless
18
+ end
19
+
20
+ ::Booth::Userland::Sessions::Transitions::Show::EnterWebauth.call request:
21
+ end
22
+
23
+ private
24
+
25
+ def authentication
26
+ request.authentication
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,50 @@
1
+ module Booth
2
+ module Userland
3
+ module Sessions
4
+ module Transitions
5
+ module Destroy
6
+ class EnterPassword
7
+ include ::Booth::Concerns::Transition
8
+
9
+ def self.applicable?(params:)
10
+ !params[:revocation]
11
+ end
12
+
13
+ def call
14
+ if sudo.password?
15
+ if session_id_param
16
+ debug { 'Having password sudo, revoking the desired session...' }
17
+ return ::Booth::Sessions::Revoke.call credential_id: authentication.credential_id,
18
+ session_id: session_id_param
19
+ else
20
+ debug { 'Having password sudo, revoking all other sessions...' }
21
+ return ::Booth::Sessions::RevokeAllOthers.call credential_id: authentication.credential_id,
22
+ surviving_session_id: authentication.session_id
23
+ end
24
+ end
25
+
26
+ if session_id_param
27
+ Tron.failure :need_sudo_to_destroy_session,
28
+ step: :enter_password_to_destroy,
29
+ session_id: session_id_param
30
+ else
31
+ Tron.failure :need_sudo_to_destroy_all_other_sessions,
32
+ step: :enter_password_to_destroy_all_others
33
+ end
34
+ end
35
+
36
+ def session_id_param
37
+ # If params[:id] is a UUID, then it's an ID for a `Booth::Models::Session` in the DB.
38
+ # If params[:id] is something else, then it's just a WebAuth Ceremony argument.
39
+ ::Booth::Syntaxes::Uuid.call(request.params[:id], raise_if_invalid: false).uuid
40
+ end
41
+
42
+ delegate :authentication, to: :request
43
+
44
+ delegate :sudo, to: :request
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,56 @@
1
+ module Booth
2
+ module Userland
3
+ module Sessions
4
+ module Transitions
5
+ module Destroy
6
+ class EnterWebauth
7
+ include ::Booth::Concerns::Transition
8
+
9
+ def self.applicable?(params:)
10
+ !params[:webauth]
11
+ end
12
+
13
+ def call
14
+ if sudo.webauth?
15
+ if session_id_param
16
+ debug { 'Having webauth sudo, revoking the desired session...' }
17
+ return ::Booth::Sessions::Revoke.call credential_id: authentication.credential_id,
18
+ session_id: session_id_param
19
+ else
20
+ debug { 'Having webauth sudo, revoking all other sessions...' }
21
+ return ::Booth::Sessions::RevokeAllOthers.call credential_id: authentication.credential_id,
22
+ surviving_session_id: authentication.session_id
23
+ end
24
+ end
25
+
26
+ if session_id_param
27
+ Tron.failure :need_sudo_to_destroy_session,
28
+ step: :enter_webauth_to_destroy,
29
+ session_id: session_id_param
30
+ else
31
+ Tron.failure :need_sudo_to_destroy_all_other_sessions,
32
+ step: :enter_webauth_to_destroy_all_others
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def session_id_param
39
+ # If params[:id] is a UUID, then it's an ID for a `Booth::Models::Session` in the DB.
40
+ # If params[:id] is something else, then it's just a WebAuth Ceremony argument.
41
+ ::Booth::Syntaxes::Uuid.call(request.params[:id], raise_if_invalid: false).uuid
42
+ end
43
+
44
+ def authentication
45
+ request.authentication
46
+ end
47
+
48
+ def sudo
49
+ request.sudo
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,83 @@
1
+ module Booth
2
+ module Userland
3
+ module Sessions
4
+ module Transitions
5
+ module Destroy
6
+ class VerifyPassword
7
+ include ::Booth::Concerns::Transition
8
+
9
+ def self.applicable?(params:)
10
+ params.dig(:revocation, :password)
11
+ end
12
+
13
+ def call
14
+ do_find_credential
15
+ .on_success { do_revoke_sessions }
16
+ end
17
+
18
+ private
19
+
20
+ def do_find_credential
21
+ @credential = ::Booth::Models::Credential.find_by(id: authentication.credential_id)
22
+ return Tron.success :found_credential if @credential
23
+
24
+ Tron.failure :missing_credential
25
+ end
26
+
27
+ def do_revoke_sessions
28
+ checking = ::Booth::Credentials::PasswordAuthentication.call(
29
+ credential: @credential,
30
+ password: password_param,
31
+ ip: request.ip,
32
+ agent: request.agent
33
+ )
34
+
35
+ if checking.success?
36
+ if session_id_param
37
+ debug { 'Having password sudo, revoking the desired session...' }
38
+ return ::Booth::Sessions::Revoke.call credential_id: authentication.credential_id,
39
+ session_id: session_id_param
40
+ else
41
+ debug { 'Having password sudo, revoking all other sessions...' }
42
+ return ::Booth::Sessions::RevokeAllOthers.call credential_id: authentication.credential_id,
43
+ surviving_session_id: authentication.session_id
44
+ end
45
+ end
46
+
47
+ public_message = checking.public_message if checking.respond_to?(:public_message)
48
+
49
+ if session_id_param
50
+ Tron.failure :need_sudo_to_destroy_session,
51
+ step: :enter_password_to_destroy,
52
+ session_id: session_id_param,
53
+ public_message: public_message
54
+ else
55
+ Tron.failure :need_sudo_to_destroy_all_other_sessions,
56
+ step: :enter_password_to_destroy_all_others,
57
+ public_message: public_message
58
+ end
59
+ end
60
+
61
+ def session_id_param
62
+ # If params[:id] is a UUID, then it's an ID for a `Booth::Models::Session` in the DB.
63
+ # If params[:id] is something else, then it's just a WebAuth Ceremony argument.
64
+ ::Booth::Syntaxes::Uuid.call(request.params[:id], raise_if_invalid: false).uuid
65
+ end
66
+
67
+ def password_param
68
+ request.params.require(:revocation).permit(:password)[:password]
69
+ end
70
+
71
+ def authentication
72
+ request.authentication
73
+ end
74
+
75
+ def sudo
76
+ request.sudo
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,38 @@
1
+ module Booth
2
+ module Userland
3
+ module Sessions
4
+ module Transitions
5
+ module Destroy
6
+ class WebauthAuthenticationInitiation
7
+ include ::Booth::Concerns::Transition
8
+
9
+ def self.applicable?(params:)
10
+ params[:webauth] && !params[:type]
11
+ end
12
+
13
+ def call
14
+ debug { 'Preparing webauth challenge...' }
15
+ credential = ::Booth::Models::Credential.find(authentication.credential_id)
16
+ challenging = Booth::Credentials::WebauthChallenge.call(credential:)
17
+ result = Tron.success :webauth_for_you, public_json: challenging.options_for_get, http_status: :ok
18
+ debug { "The challenge is #{challenging.challenge}" }
19
+ sudo.webauthn_challenge = challenging.challenge
20
+ debug { "Responding with JSON: #{result.public_json}" }
21
+ result
22
+ end
23
+
24
+ private
25
+
26
+ def sudo
27
+ request.sudo
28
+ end
29
+
30
+ def authentication
31
+ request.authentication
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,61 @@
1
+ module Booth
2
+ module Userland
3
+ module Sessions
4
+ module Transitions
5
+ module Destroy
6
+ class WebauthAuthenticationVerification
7
+ include ::Booth::Concerns::Transition
8
+
9
+ def self.applicable?(params:)
10
+ params[:webauth] && params[:type]
11
+ end
12
+
13
+ def call
14
+ do_find_challenge
15
+ .on_success { do_check_webauth }
16
+ end
17
+
18
+ # Helpers
19
+
20
+ def do_find_challenge
21
+ return Tron.success :challenge_ongoing if sudo.webauthn_challenge.present?
22
+
23
+ debug { 'There is no corresponding challenge in the session' }
24
+ Tron.failure :no_session_challenge, public_json: {}, http_status: :unprocessable_entity
25
+ end
26
+
27
+ def do_check_webauth
28
+ verification = ::Booth::Webauth::AuthenticationVerification.call(
29
+ request:,
30
+ credential_id: authentication.credential_id,
31
+ challenge: sudo.webauthn_challenge
32
+ )
33
+ return verification if verification.failure?
34
+
35
+ if session_id_param
36
+ ::Booth::Sessions::Revoke.call credential_id: authentication.credential_id,
37
+ session_id: session_id_param
38
+ else
39
+ ::Booth::Sessions::RevokeAllOthers.call credential_id: authentication.credential_id,
40
+ surviving_session_id: authentication.session_id
41
+ end
42
+
43
+ Tron.success :session_revocation_successful, public_json: {},
44
+ http_status: :created
45
+ end
46
+
47
+ def session_id_param
48
+ # If params[:id] is a UUID, then it's an ID for a `Booth::Models::Session` in the DB.
49
+ # If params[:id] is something else, then it's just a WebAuth Ceremony argument.
50
+ ::Booth::Syntaxes::Uuid.call(request.params[:id], raise_if_invalid: false).uuid
51
+ end
52
+
53
+ delegate :authentication, to: :request
54
+
55
+ delegate :sudo, to: :request
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end