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,28 @@
1
+ module Booth
2
+ module Credentials
3
+ class Create
4
+ include ::Booth::MethodObject
5
+ include ::Booth::Logging
6
+
7
+ option :username
8
+ option :allowed_modes
9
+ option :scope, default: -> { :default }
10
+
11
+ def call
12
+ if credential.save
13
+ debug { "Successfully persisted a new Booth::Models::Credential record with ID #{credential.id.inspect}" }
14
+ Tron.success :credential_created, credential:
15
+ else
16
+ debug { 'Could not persist a new Booth::Models::Credential record' }
17
+ Tron.failure :persistence_failed, error: credential.errors.full_messages.to_sentence
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def credential
24
+ @credential ||= ::Booth::Models::Credential.new(username:, allowed_modes:, scope:)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ module Booth
2
+ module Credentials
3
+ class CreateWithOnboarding
4
+ include ::Booth::MethodObject
5
+ include ::Booth::Logging
6
+
7
+ option :username
8
+ option :allowed_modes
9
+ option :scope, default: -> { :default }
10
+
11
+ def call
12
+ creation = ::Booth::Credentials::Create.call(username:, allowed_modes:, scope:)
13
+
14
+ creation.on_success do
15
+ onboarding = creation.credential.create_onboarding!
16
+
17
+ return Tron.success :credential_with_onboarding_created,
18
+ credential: creation.credential,
19
+ onboarding:
20
+ end
21
+
22
+ creation
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,45 @@
1
+ module Booth
2
+ module Credentials
3
+ class FindByUsername
4
+ include ::Booth::Logging
5
+ include ::Booth::MethodObject
6
+
7
+ option :username
8
+
9
+ def call
10
+ do_check_username_syntax
11
+ .on_success { do_find_credential }
12
+ end
13
+
14
+ private
15
+
16
+ def do_check_username_syntax
17
+ checking = ::Booth::Syntaxes::Username.call(username)
18
+ @normalized_username = checking.normalized_username
19
+ @normalized_invalid_username = checking.normalized_invalid_username
20
+
21
+ checking
22
+ end
23
+
24
+ def do_find_credential
25
+ if credential
26
+ Tron.success :found_existing_credential, credential: credential,
27
+ normalized_username: @normalized_username,
28
+ normalized_invalid_username: @normalized_invalid_username
29
+ else
30
+ Tron.failure :credential_not_found, normalized_username: @normalized_username,
31
+ normalized_invalid_username: @normalized_invalid_username,
32
+ public_message: I18n.t('booth.unknown_username')
33
+ end
34
+ end
35
+
36
+ # Helpers
37
+
38
+ def credential
39
+ return @credential if defined?(@credential)
40
+
41
+ @credential = ::Booth::Models::Credential.find_by(username: @normalized_username)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,69 @@
1
+ module Booth
2
+ module Credentials
3
+ class Mode
4
+ include ::Booth::Logging
5
+
6
+ def initialize(credential)
7
+ @credential = credential
8
+ end
9
+
10
+ # OTP
11
+
12
+ def otp_addable?
13
+ ::Booth::Credentials::Modes::OtpAddable.call(credential)
14
+ end
15
+
16
+ def otp_changeable?
17
+ ::Booth::Credentials::Modes::OtpChangeable.call(credential)
18
+ end
19
+
20
+ def otp_removable?
21
+ ::Booth::Credentials::Modes::OtpRemovable.call(credential)
22
+ end
23
+
24
+ def otp_manageable?
25
+ ::Booth::Credentials::Modes::OtpManageable.call(credential)
26
+ end
27
+
28
+ # Password
29
+
30
+ def password_addable?
31
+ ::Booth::Credentials::Modes::PasswordAddable.call(credential)
32
+ end
33
+
34
+ def password_changeable?
35
+ ::Booth::Credentials::Modes::PasswordChangeable.call(credential)
36
+ end
37
+
38
+ def password_removable?
39
+ ::Booth::Credentials::Modes::PasswordRemovable.call(credential)
40
+ end
41
+
42
+ def password_removal_requires_user_verifiable_webauth?
43
+ ::Booth::Credentials::Modes::PasswordRemovalRequiresUserVerifiableWebauth.call(credential)
44
+ end
45
+
46
+ def password_manageable?
47
+ ::Booth::Credentials::Modes::PasswordManageable.call(credential)
48
+ end
49
+
50
+ # Webauth
51
+
52
+ def webauth_addable?
53
+ ::Booth::Credentials::Modes::WebauthAddable.call(credential)
54
+ end
55
+
56
+ def webauth_removable?
57
+ ::Booth::Credentials::Modes::WebauthRemovable.call(credential)
58
+ end
59
+
60
+ def webauth_manageable?
61
+ ::Booth::Credentials::Modes::WebauthManageable.call(credential)
62
+ end
63
+
64
+ private
65
+
66
+ attr_reader :credential
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,23 @@
1
+ module Booth
2
+ module Credentials
3
+ module Modes
4
+ class OtpAddable
5
+ include ::Booth::MethodObject
6
+
7
+ param :credential
8
+
9
+ def call
10
+ !why_not?
11
+ end
12
+
13
+ private
14
+
15
+ def why_not?
16
+ return :first_time_credential if credential.mode_first_time?
17
+ return :already_exists if credential.mode_username_password_and_otp?
18
+ return :otp_not_allowed unless credential.allows_mode_username_password_and_otp?
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module Booth
2
+ module Credentials
3
+ module Modes
4
+ class OtpChangeable
5
+ include ::Booth::MethodObject
6
+
7
+ param :credential
8
+
9
+ def call
10
+ !why_not?
11
+ end
12
+
13
+ private
14
+
15
+ def why_not?
16
+ return :first_time_credential if credential.mode_first_time?
17
+ return :no_otp_in_use unless credential.mode_username_password_and_otp?
18
+ return :otp_deprecated unless credential.allows_mode_username_password_and_otp?
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module Booth
2
+ module Credentials
3
+ module Modes
4
+ class OtpManageable
5
+ include ::Booth::MethodObject
6
+
7
+ param :credential
8
+
9
+ def call
10
+ ::Booth::Credentials::Modes::OtpAddable.call(credential) ||
11
+ ::Booth::Credentials::Modes::OtpChangeable.call(credential) ||
12
+ ::Booth::Credentials::Modes::OtpRemovable.call(credential)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module Booth
2
+ module Credentials
3
+ module Modes
4
+ class OtpRemovable
5
+ include ::Booth::MethodObject
6
+
7
+ param :credential
8
+
9
+ def call
10
+ !why_not?
11
+ end
12
+
13
+ private
14
+
15
+ def why_not?
16
+ return :first_time_credential if credential.mode_first_time?
17
+ return :otp_not_in_use unless credential.mode_username_password_and_otp?
18
+ return :otp_required unless credential.allows_mode_username_and_password?
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ module Booth
2
+ module Credentials
3
+ module Modes
4
+ class PasswordAddable
5
+ include ::Booth::MethodObject
6
+
7
+ param :credential
8
+
9
+ def call
10
+ !why_not?
11
+ end
12
+
13
+ private
14
+
15
+ def why_not?
16
+ return :first_time_credential_is_passwordless if credential.mode_first_time? &&
17
+ !credential.mode_username_and_password? &&
18
+ !credential.mode_username_password_and_otp? &&
19
+ !credential.mode_username_password_and_webauth?
20
+ return :password_with_username_in_use if credential.mode_username_and_password?
21
+ return :password_with_otp_in_use if credential.mode_username_password_and_otp?
22
+ return :password_with_webauth_in_use if credential.mode_username_password_and_webauth?
23
+
24
+ return :not_allowed_with_webauth unless credential.allows_mode_username_password_and_webauth?
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ module Booth
2
+ module Credentials
3
+ module Modes
4
+ class PasswordChangeable
5
+ include ::Booth::Logging
6
+ include ::Booth::MethodObject
7
+
8
+ param :credential
9
+
10
+ def call
11
+ !why_not?
12
+ end
13
+
14
+ private
15
+
16
+ def why_not?
17
+ return :first_time_credential if credential.mode_first_time?
18
+ return :username_and_password_deprecated if credential.mode_username_and_password? &&
19
+ !credential.allows_mode_username_and_password?
20
+ return :username_and_password_and_otp_deprecated if credential.mode_username_password_and_otp? &&
21
+ !credential.allows_mode_username_password_and_otp?
22
+ return :username_password_and_webauth_deprecated if credential.mode_username_password_and_webauth? &&
23
+ !credential.allows_mode_username_password_and_webauth?
24
+ return :password_not_in_use unless credential.mode_username_and_password? ||
25
+ credential.mode_username_password_and_otp? ||
26
+ credential.mode_username_password_and_webauth?
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ module Booth
2
+ module Credentials
3
+ module Modes
4
+ class PasswordManageable
5
+ include ::Booth::MethodObject
6
+
7
+ param :credential
8
+
9
+ def call
10
+ ::Booth::Credentials::Modes::PasswordAddable.call(credential) ||
11
+ ::Booth::Credentials::Modes::PasswordChangeable.call(credential) ||
12
+ ::Booth::Credentials::Modes::PasswordRemovable.call(credential)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ module Booth
2
+ module Credentials
3
+ module Modes
4
+ class PasswordRemovable
5
+ include ::Booth::Logging
6
+ include ::Booth::MethodObject
7
+
8
+ param :credential
9
+
10
+ def call
11
+ !why_not?
12
+ end
13
+
14
+ private
15
+
16
+ def why_not?
17
+ return :first_time_credential if credential.mode_first_time?
18
+ return :webauth_needed unless credential.mode_username_password_and_webauth?
19
+ return :not_downgradable unless credential.allows_mode_username_and_webauth?
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ module Booth
2
+ module Credentials
3
+ module Modes
4
+ class PasswordRemovalRequiresUserVerifiableWebauth
5
+ include ::Booth::Logging
6
+ include ::Booth::MethodObject
7
+
8
+ param :credential
9
+
10
+ def call
11
+ ::Booth::Credentials::Modes::PasswordRemovable.call(credential) == :need_user_verifiable_authenticators
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ module Booth
2
+ module Credentials
3
+ module Modes
4
+ class WebauthAddable
5
+ include ::Booth::MethodObject
6
+
7
+ param :credential
8
+
9
+ def call
10
+ !why_not?
11
+ end
12
+
13
+ private
14
+
15
+ def why_not?
16
+ return :first_time_credential_needs_password if credential.mode_first_time? &&
17
+ !credential.allows_mode_username_and_webauth?
18
+
19
+ return :webauth_not_allowed unless credential.mode_first_time? ||
20
+ credential.allows_mode_username_password_and_webauth? ||
21
+ credential.allows_mode_username_and_webauth?
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ module Booth
2
+ module Credentials
3
+ module Modes
4
+ class WebauthManageable
5
+ include ::Booth::MethodObject
6
+
7
+ param :credential
8
+
9
+ def call
10
+ ::Booth::Credentials::Modes::WebauthAddable.call(credential) ||
11
+ ::Booth::Credentials::Modes::WebauthRemovable.call(credential)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,25 @@
1
+ module Booth
2
+ module Credentials
3
+ module Modes
4
+ class WebauthRemovable
5
+ include ::Booth::MethodObject
6
+
7
+ param :credential
8
+
9
+ def call
10
+ return true if credential.authenticators.registered_scope.size > 1
11
+
12
+ !why_not?
13
+ end
14
+
15
+ private
16
+
17
+ def why_not?
18
+ return :first_time_credential if credential.mode_first_time?
19
+ return :webauth_not_secondary_factor unless credential.mode_username_password_and_webauth?
20
+ return :webauth_as_secondary_required unless credential.allows_mode_username_and_password?
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,59 @@
1
+ module Booth
2
+ module Credentials
3
+ class OtpAuthentication
4
+ include ::Booth::MethodObject
5
+ include ::Booth::Logging
6
+
7
+ option :credential
8
+ option :code
9
+ option :ip
10
+ option :agent
11
+
12
+ def call
13
+ do_check_otp_code_syntax
14
+ .on_success { do_check_cooldown }
15
+ .on_success { do_check_code }
16
+ end
17
+
18
+ private
19
+
20
+ def do_check_otp_code_syntax
21
+ check = ::Booth::Syntaxes::Otp.call(code)
22
+ return check if check.failure?
23
+
24
+ @normalized_otp_code = check.normalized_otp
25
+ check
26
+ end
27
+
28
+ def do_check_cooldown
29
+ ::Booth::Cooldowns::Otp.call credential:
30
+ end
31
+
32
+ def do_check_code
33
+ return correct_code! if credential.authenticate_otp(@normalized_otp_code)
34
+
35
+ wrong_code!
36
+ end
37
+
38
+ def correct_code!
39
+ debug { 'The provided OTP is correct' }
40
+ ::Booth::Audits::Register::CorrectOtp.call credential: credential,
41
+ ip: ip,
42
+ agent: agent
43
+ Tron.success :correct_otp
44
+ end
45
+
46
+ def wrong_code!
47
+ debug { "The provided OTP #{@normalized_otp_code} is wrong. It should have been #{credential.otp_code}" }
48
+ ::Booth::Audits::Register::WrongOtp.call credential: credential,
49
+ ip: ip,
50
+ agent: agent
51
+
52
+ cooldown = ::Booth::Cooldowns::Otp.call credential: credential
53
+ return Tron.failure(:wrong_password, public_message: I18n.t('booth.wrong_otp')) if cooldown.success?
54
+
55
+ cooldown
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,72 @@
1
+ module Booth
2
+ module Credentials
3
+ class PasswordAuthentication
4
+ include ::Booth::MethodObject
5
+ include ::Booth::Logging
6
+
7
+ option :credential
8
+ option :password
9
+ option :ip
10
+ option :agent
11
+
12
+ def call
13
+ do_check_blank_password
14
+ .on_success { do_check_too_short_password }
15
+ .on_success { do_check_cooldown }
16
+ .on_success { do_check_password }
17
+ end
18
+
19
+ private
20
+
21
+ def do_check_blank_password
22
+ return Tron.success :password_is_present if password.present?
23
+
24
+ debug { 'The provided password is blank' }
25
+ Tron.failure :blank_password, public_message: I18n.t('booth.blank_password')
26
+ end
27
+
28
+ def do_check_too_short_password
29
+ return Tron.success :password_is_long_enough if password.to_s.length >= credential.class.password_minlength
30
+
31
+ debug { 'The provided password is too short' }
32
+ Tron.failure :short_password, public_message: I18n.t('booth.short_password',
33
+ minlength: credential.class.password_minlength)
34
+ end
35
+
36
+ def do_check_cooldown
37
+ cooldown = ::Booth::Cooldowns::Password.call(ip:, credential:)
38
+ return cooldown if cooldown.success?
39
+
40
+ cooldown.public_message.prepend "#{I18n.t('booth.attempt_was_ignored')} "
41
+ cooldown
42
+ end
43
+
44
+ def do_check_password
45
+ return correct_pasword! if credential.authenticate(password)
46
+
47
+ wrong_password!
48
+ end
49
+
50
+ def correct_pasword!
51
+ debug { 'The provided password is correct' }
52
+ ::Booth::Audits::Register::CorrectPassword.call credential: credential,
53
+ ip: ip,
54
+ agent: agent
55
+ Tron.success :correct_password
56
+ end
57
+
58
+ def wrong_password!
59
+ debug { 'The provided password is wrong' }
60
+ ::Booth::Audits::Register::WrongPassword.call credential: credential,
61
+ ip: ip,
62
+ agent: agent
63
+
64
+ cooldown = ::Booth::Cooldowns::Password.call ip: ip, credential: credential
65
+ return Tron.failure(:wrong_password, public_message: I18n.t('booth.wrong_password')) if cooldown.success?
66
+
67
+ cooldown.public_message.prepend "#{I18n.t('booth.wrong_password')} "
68
+ cooldown
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,28 @@
1
+ module Booth
2
+ module Credentials
3
+ # See https://github.com/cedarcode/webauthn-rails-demo-app/blob/d9b73e20a7272e8d9f7a26c48ec49e5100295939/app/controllers/sessions_controller.rb#L7-L23
4
+ class WebauthChallenge
5
+ include ::Booth::Logging
6
+ include ::Booth::MethodObject
7
+
8
+ option :credential
9
+
10
+ def call
11
+ return Tron.failure :missing_credential, challenge: nil if credential.nil?
12
+ return Tron.failure :no_known_authenticators, challenge: nil if credential.registered_authenticator_ids.blank?
13
+
14
+ Tron.success :here_is_your_challenge, options_for_get: options_for_get.to_json,
15
+ challenge: options_for_get.challenge
16
+ end
17
+
18
+ private
19
+
20
+ def options_for_get
21
+ @options_for_get ||= ::Booth::Webauth::OptionsForGet.call(
22
+ allowed_device_ids: credential.registered_authenticator_ids,
23
+ requires_user_verification: credential.requires_user_verification?
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ module Booth
2
+ class Engine < ::Rails::Engine
3
+ initializer 'booth.add_manifest' do |app|
4
+ app.config.assets.precompile << 'booth_manifest.js'
5
+ end
6
+
7
+ initializer 'booth.insert_warden_middleware' do |app|
8
+ app.config.middleware.insert_after ::ActionDispatch::Flash, ::Warden::Manager do |manager|
9
+ manager.intercept_401 = false
10
+ manager.serialize_into_session { ::Booth::Hooks::SerializeIntoSession.call(_1) }
11
+ manager.serialize_from_session { ::Booth::Hooks::SerializeFromSession.call(_1) }
12
+ end
13
+ end
14
+
15
+ initializer 'booth.register_warden_hooks' do
16
+ ::Warden::Manager.after_fetch do |passport, warden, options|
17
+ ::Booth::Hooks::AfterFetch.call passport:, warden:, options:
18
+ end
19
+
20
+ ::Warden::Manager.before_logout do |passport, warden, options|
21
+ ::Booth::Hooks::BeforeLogout.call passport:, warden:, options:
22
+ end
23
+ end
24
+ end
25
+ end