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,63 @@
1
+ module Booth
2
+ module Adminland
3
+ module Onboardings
4
+ class Create
5
+ include ::Booth::MethodObject
6
+ include ::Booth::Logging
7
+
8
+ option :credential_id
9
+
10
+ def call
11
+ find_action.on_success { validate_action }
12
+ .on_success { save_action }
13
+ end
14
+
15
+ private
16
+
17
+ def find_action
18
+ if credential
19
+ debug { "Found the credential with id #{credential_id.inspect}, deleting any current onboardings" }
20
+ ::Booth::Models::Onboarding.where(credential_id: credential.id).destroy_all
21
+ Tron.success :credential_exists
22
+ else
23
+ warn { "Could not find credential with id #{credential_id.inspect}" }
24
+ Tron.failure :credential_not_found
25
+ end
26
+ end
27
+
28
+ def validate_action
29
+ if onboarding.valid?
30
+ debug { 'The new onboarding record is valid' }
31
+ Tron.success :record_is_valid
32
+ else
33
+ debug { "The new onboarding record is invalid: #{onboarding.errors.full_messages.to_sentence}" }
34
+ Tron.failure :invalid_record, message: onboarding.errors.full_messages.to_sentence
35
+ end
36
+ end
37
+
38
+ def save_action
39
+ if onboarding.save
40
+ debug { 'Successfully persisted a new onboarding record' }
41
+ Tron.success :onboarding_created, id: onboarding.id, credential_id: credential.id,
42
+ secret_key: onboarding.secret_key
43
+ else
44
+ debug { 'Could not persist a new onboarding record' }
45
+ Tron.failure :persistence_failed, message: onboarding.errors.full_messages.to_sentence
46
+ end
47
+ end
48
+
49
+ def credential
50
+ return @credential if defined?(@credential)
51
+
52
+ @credential = ::Booth::Models::Credential.find_by(id: credential_id)
53
+ end
54
+
55
+ def onboarding
56
+ @onboarding ||= ::Booth::Models::Onboarding.new(
57
+ credential_id:
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,50 @@
1
+ module Booth
2
+ module Adminland
3
+ module Onboardings
4
+ class Destroy
5
+ include ::Booth::Logging
6
+ include ::Booth::MethodObject
7
+
8
+ option :id, as: :raw_id
9
+
10
+ def call
11
+ do_verify_id
12
+ .on_success { do_find_record }
13
+ .on_success { do_destroy_record }
14
+ end
15
+
16
+ def do_verify_id
17
+ return Tron.success :valid_id_syntax if id
18
+
19
+ debug { "Invalid Onboarding ID #{raw_id.inspect}" }
20
+ Tron.failure :invalid_id_syntax, id:, credential_id: nil
21
+ end
22
+
23
+ def do_find_record
24
+ return Tron.success :record_found if record
25
+
26
+ debug { "Could not find Onboarding with ID #{id.inspect}" }
27
+ Tron.failure :onboarding_not_found, id: nil, credential_id: nil
28
+ end
29
+
30
+ def do_destroy_record
31
+ return Tron.success(:onboarding_deleted, id: record.id, credential_id:) if record.destroy
32
+
33
+ Tron.failure :could_not_delete_onboarding, id: record.id, credential_id:
34
+ end
35
+
36
+ private
37
+
38
+ delegate :credential_id, to: :record, allow_nil: true
39
+
40
+ def id
41
+ ::Booth::Syntaxes::Uuid.call(raw_id, raise_if_invalid: false).uuid
42
+ end
43
+
44
+ def record
45
+ @record ||= ::Booth::Models::Onboarding.find_by(id:)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,93 @@
1
+ module Booth
2
+ module Adminland
3
+ module Onboardings
4
+ class Find
5
+ include ::Booth::Logging
6
+ include ::Booth::MethodObject
7
+
8
+ option :id, default: -> {}
9
+ option :credential_id, default: -> {}, as: :raw_credential_id
10
+ option :raise_if_missing, default: -> { false }
11
+ option :only_unused, default: -> { false }
12
+
13
+ def call
14
+ do_check_id
15
+ .on_success { do_find_onboarding }
16
+ .on_success { do_check_used }
17
+ .on_success { do_serialize_onboarding }
18
+ end
19
+
20
+ private
21
+
22
+ def do_check_id
23
+ return Tron.success(:id_is_valid) if onboarding_id || credential_id
24
+
25
+ Tron.failure :no_id_provided, record: nil
26
+ end
27
+
28
+ def do_find_onboarding
29
+ @onboarding = if onboarding_id
30
+ ::Booth::Models::Onboarding.find_by(id: onboarding_id)
31
+ else
32
+ ::Booth::Models::Onboarding.find_by(credential_id:)
33
+ end
34
+
35
+ return Tron.success :onboarding_exists if @onboarding
36
+
37
+ if onboarding_id
38
+ debug { "Couldn't find Onboarding with ID #{onboarding_id.inspect}" }
39
+ else
40
+ debug { "Couldn't find Onboarding with credential ID #{credential_id.inspect}" }
41
+ end
42
+
43
+ result = Tron.failure :onboarding_not_found, provided_onboarding_id: id.inspect,
44
+ provided_credential_id: raw_credential_id.inspect,
45
+ record: nil
46
+ raise result if raise_if_missing
47
+
48
+ result
49
+ end
50
+
51
+ def do_check_used
52
+ return Tron.success :onboarding_is_new if @onboarding.step == :choose_mode
53
+ return Tron.success :we_allow_used_onboardings unless only_unused
54
+
55
+ error = Tron.failure(:onboarding_already_used, provided_onboarding_id: id.inspect,
56
+ provided_credential_id: raw_credential_id.inspect,
57
+ record: nil)
58
+ raise error if raise_if_missing
59
+
60
+ error
61
+ end
62
+
63
+ def do_serialize_onboarding
64
+ attributes = @onboarding.attributes
65
+ .symbolize_keys
66
+ .slice(:id, :credential_id, :secret_key, :accessed_at, :mode)
67
+ .merge(additional_attributes)
68
+
69
+ debug { "Onboarding found ID #{@onboarding.id.inspect} and Credential ID #{@onboarding.credential_id.inspect}" }
70
+ Tron.success(:onboarding_found, record: ::Booth::ToStruct.call(attributes))
71
+ end
72
+
73
+ # Helpers
74
+
75
+ def onboarding_id
76
+ ::Booth::Syntaxes::Uuid.call(id, raise_if_invalid: false).uuid
77
+ end
78
+
79
+ def credential_id
80
+ ::Booth::Syntaxes::Uuid.call(raw_credential_id, raise_if_invalid: false).uuid
81
+ end
82
+
83
+ def additional_attributes
84
+ {
85
+ step: @onboarding.step,
86
+ username: @onboarding.username,
87
+ is_completed: @onboarding.completed?,
88
+ }
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,23 @@
1
+ module Booth
2
+ module Adminland
3
+ module Onboardings
4
+ class Index
5
+ include ::Booth::MethodObject
6
+
7
+ option :scope, ->(scope) { ::Booth::Syntaxes::Scope.call(scope).normalized_scope }
8
+
9
+ def call
10
+ Tron.success(:all_onboardings, records:)
11
+ end
12
+
13
+ private
14
+
15
+ def records
16
+ ::Booth::Models::Onboarding.includes_scope
17
+ .sorted_scope
18
+ .where(credential: { scope: })
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ module Booth
2
+ module Adminland
3
+ class PeriodicCleanup
4
+ include ::Booth::MethodObject
5
+
6
+ def call
7
+ # TODO
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,70 @@
1
+ module Booth
2
+ module Adminland
3
+ module Recoveries
4
+ # When sending out username recovery emails,
5
+ # this class helps to keep the sending mechanism idempodent.
6
+ class Consume
7
+ include ::Booth::MethodObject
8
+ include ::Booth::Logging
9
+
10
+ option :recovery_id
11
+ option :credential_id
12
+
13
+ def call
14
+ do_find_recovery
15
+ .on_success { do_find_credential }
16
+ .on_success { do_check_eligibility }
17
+ .on_success { do_consume }
18
+ end
19
+
20
+ private
21
+
22
+ def do_find_recovery
23
+ debug { "Looking for Recovery with ID #{recovery_id.inspect}" }
24
+ @recovery = ::Booth::Models::Recovery.find_by(id: recovery_id)
25
+
26
+ if @recovery
27
+ debug { 'Recovery was found' }
28
+ return Tron.success :recovery_exists
29
+ end
30
+
31
+ debug { 'Did not find Recovery' }
32
+ Tron.failure :recovery_not_found, provided_id: recovery_id.inspect
33
+ end
34
+
35
+ def do_find_credential
36
+ debug { "Looking for Credential with ID #{credential_id.inspect}" }
37
+ @credential = ::Booth::Models::Credential.find_by(id: credential_id)
38
+
39
+ if @credential
40
+ debug { 'Credential was found' }
41
+ return Tron.success :credential_exists
42
+ end
43
+
44
+ debug { 'Did not find Credential' }
45
+ Tron.failure :credential_not_found, provided_id: credential_id.inspect
46
+ end
47
+
48
+ def do_check_eligibility
49
+ return Tron.failure :already_consumed if @recovery.consumed?
50
+
51
+ return Tron.failure :already_revoked if @recovery.revoked?
52
+
53
+ Tron.success :can_be_consumed
54
+ end
55
+
56
+ def do_consume
57
+ @recovery.transaction do
58
+ @recovery.update! consumed_at: Time.current
59
+
60
+ @recovery.other_recoveries_with_this_scope_and_email
61
+ .update_all revoked_at: Time.current # rubocop:disable Rails/SkipsModelValidations
62
+ end
63
+
64
+ Tron.success :recovery_consumed, email: @recovery.email,
65
+ username: @credential.username
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,48 @@
1
+ module Booth
2
+ module Adminland
3
+ # ------------------
4
+ # Credential Helpers
5
+ # ------------------
6
+
7
+ def self.create_credential(...)
8
+ ::Booth::Adminland::Credentials::Create.call(...)
9
+ end
10
+
11
+
12
+ # ------------------
13
+ # Onboarding Helpers
14
+ # ------------------
15
+
16
+ def self.create_onboarding(...)
17
+ ::Booth::Adminland::Onboardings::Create.call(...)
18
+ end
19
+
20
+ def self.destroy_onboarding(...)
21
+ ::Booth::Adminland::Onboardings::Destroy.call(...)
22
+ end
23
+
24
+ def self.find_onboarding(...)
25
+ ::Booth::Adminland::Onboardings::Find.call(...)
26
+ end
27
+
28
+ def self.find_onboarding_for_mail_delivery(id:)
29
+ ::Booth::Adminland::Onboardings::Find.call(id:, only_unused: true, raise_if_missing: true)
30
+ end
31
+
32
+ def self.index_onboardings(...)
33
+ ::Booth::Adminland::Onboardings::Index.call(...)
34
+ end
35
+
36
+
37
+
38
+ # Other
39
+
40
+ def self.consume_recovery(...)
41
+ ::Booth::Adminland::Recoveries::Consume.call(...)
42
+ end
43
+
44
+ def self.periodic_cleanup(...)
45
+ ::Booth::Adminland::PeriodicCleanup.call(...)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,22 @@
1
+ module Booth
2
+ module Audits
3
+ module Register
4
+ class AddedOtp
5
+ include ::Booth::MethodObject
6
+
7
+ option :credential
8
+ option :ip
9
+ option :agent
10
+
11
+ def call
12
+ ::Booth::Models::Audit.create! credential: credential,
13
+ ip: ip,
14
+ agent: agent,
15
+ event: :added_otp
16
+
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Booth
2
+ module Audits
3
+ module Register
4
+ class ChangedOtp
5
+ include ::Booth::MethodObject
6
+
7
+ option :credential
8
+ option :ip
9
+ option :agent
10
+
11
+ def call
12
+ ::Booth::Models::Audit.create! credential: credential,
13
+ ip: ip,
14
+ agent: agent,
15
+ event: :changed_otp
16
+
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Booth
2
+ module Audits
3
+ module Register
4
+ class CompletedOnboarding
5
+ include ::Booth::MethodObject
6
+
7
+ option :credential
8
+ option :ip
9
+ option :agent
10
+
11
+ def call
12
+ ::Booth::Models::Audit.create! credential: credential,
13
+ ip: ip,
14
+ agent: agent,
15
+ event: :completed_onboarding
16
+
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,42 @@
1
+ module Booth
2
+ module Audits
3
+ module Register
4
+ class CorrectOtp
5
+ include ::Booth::MethodObject
6
+ include ::Booth::Logging
7
+
8
+ option :credential
9
+ option :ip
10
+ option :agent
11
+
12
+ def call
13
+ ::Booth::Models::Audit.transaction do
14
+ register_attempt!
15
+ clear_failed_attempts!
16
+ end
17
+
18
+ nil
19
+ end
20
+
21
+ private
22
+
23
+ def register_attempt!
24
+ debug { "Auditing correct OTP for credential `#{credential.id}` and IP `#{ip}`" }
25
+
26
+ ::Booth::Models::Audit.create! credential:,
27
+ ip:,
28
+ agent:,
29
+ event: :entered_correct_password
30
+ end
31
+
32
+ def clear_failed_attempts!
33
+ debug { "Redeeming all failed OTP attempts for credential `#{credential.id}`" }
34
+
35
+ ::Booth::Models::Audit.where(credential:)
36
+ .where(event: :entered_wrong_otp)
37
+ .update_all(deleted_at: Time.current) # rubocop:disable Rails/SkipsModelValidations
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,43 @@
1
+ module Booth
2
+ module Audits
3
+ module Register
4
+ class CorrectPassword
5
+ include ::Booth::MethodObject
6
+ include ::Booth::Logging
7
+
8
+ option :credential
9
+ option :ip
10
+ option :agent
11
+
12
+ def call
13
+ ::Booth::Models::Audit.transaction do
14
+ register_attempt!
15
+ clear_failed_attempts!
16
+ end
17
+
18
+ nil
19
+ end
20
+
21
+ private
22
+
23
+ def register_attempt!
24
+ debug { "Auditing correct password for credential `#{credential.id}` and IP `#{ip}`" }
25
+
26
+ ::Booth::Models::Audit.create! credential:,
27
+ ip:,
28
+ agent:,
29
+ event: :entered_correct_password
30
+ end
31
+
32
+ def clear_failed_attempts!
33
+ debug { "Redeeming failed password attempts for credential `#{credential.id}` and IP `#{ip}`" }
34
+
35
+ ::Booth::Models::Audit.where(credential:)
36
+ .where(ip:)
37
+ .where(event: :entered_wrong_password)
38
+ .update_all(deleted_at: Time.current) # rubocop:disable Rails/SkipsModelValidations
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ module Booth
2
+ module Audits
3
+ module Register
4
+ class Logout
5
+ include ::Booth::MethodObject
6
+
7
+ option :credential
8
+ option :ip
9
+ option :agent
10
+
11
+ def call
12
+ ::Booth::Models::Audit.create! credential: credential,
13
+ ip: ip,
14
+ agent: agent,
15
+ event: :logout
16
+
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Booth
2
+ module Audits
3
+ module Register
4
+ class RequestedPasswordReset
5
+ include ::Booth::MethodObject
6
+
7
+ option :credential
8
+ option :ip
9
+ option :agent
10
+
11
+ def call
12
+ ::Booth::Models::Audit.create! credential: credential,
13
+ ip: ip,
14
+ agent: agent,
15
+ event: :requested_password_reset
16
+
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Booth
2
+ module Audits
3
+ module Register
4
+ class WrongOtp
5
+ include ::Booth::MethodObject
6
+
7
+ option :credential
8
+ option :ip
9
+ option :agent
10
+
11
+ def call
12
+ ::Booth::Models::Audit.create! credential: credential,
13
+ ip: ip,
14
+ agent: agent,
15
+ event: :entered_wrong_otp
16
+
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ module Booth
2
+ module Audits
3
+ module Register
4
+ class WrongPassword
5
+ include ::Booth::MethodObject
6
+ include ::Booth::Logging
7
+
8
+ option :credential
9
+ option :ip
10
+ option :agent
11
+
12
+ def call
13
+ debug { 'Auditing wrong password...' }
14
+
15
+ ::Booth::Models::Audit.create! credential: credential,
16
+ ip: ip,
17
+ agent: agent,
18
+ event: :entered_wrong_password
19
+
20
+ nil
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ module Booth
2
+ module Authenticators
3
+ class Confirm
4
+ include ::Booth::MethodObject
5
+ include ::Booth::Logging
6
+
7
+ option :authenticator
8
+ option :sign_count
9
+
10
+ def call
11
+ raise 'Authenticator is already confirmed' if authenticator.confirmed_at
12
+
13
+ debug { 'Confirming Authenticator...' }
14
+ authenticator.transaction do
15
+ authenticator.update! sign_count: sign_count,
16
+ confirmed_at: Time.current
17
+
18
+ authenticator.credential.update! mode: new_mode
19
+ end
20
+
21
+ Tron.success :confirmation_successful
22
+ rescue ActiveRecord::ActiveRecordError => e
23
+ error { e }
24
+ Tron.failure :confirmation_failed
25
+ end
26
+
27
+ private
28
+
29
+ def new_mode
30
+ ::Booth::Authenticators::CredentialModeAfterConfirmation.call(authenticator:)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ module Booth
2
+ module Authenticators
3
+ class CredentialModeAfterConfirmation
4
+ include ::Booth::MethodObject
5
+
6
+ option :authenticator
7
+
8
+ def call
9
+ return :username_and_webauth if credential.mode_username_and_webauth?
10
+ return :username_password_and_webauth if credential.mode_username_password_and_webauth?
11
+
12
+ return :username_and_webauth if credential.mode_first_time? &&
13
+ authenticator.supports_user_verification?
14
+
15
+ return :username_password_and_webauth if credential.mode_username_and_password? ||
16
+ credential.mode_username_password_and_otp?
17
+
18
+ raise "Cannot add webauth for credential #{credential.id} with mode #{credential.mode} " \
19
+ "and authenticator #{authenticator.id} (user verification: #{authenticator.supports_user_verification?})"
20
+ end
21
+
22
+ delegate :credential, to: :authenticator
23
+ end
24
+ end
25
+ end