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,36 @@
1
+ module Booth
2
+ module Userland
3
+ module Webauths
4
+ module Transitions
5
+ module Create
6
+ class Reset
7
+ include ::Booth::Concerns::Transition
8
+
9
+ def self.applicable?(params:)
10
+ params.key?(:reset)
11
+ end
12
+
13
+ def call
14
+ do_destroy_authenticator
15
+ end
16
+
17
+ private
18
+
19
+ def do_destroy_authenticator
20
+ if storage.authenticator.destroy
21
+ storage.authenticator_id = nil
22
+ return Tron.success :draft_authenticator_removed
23
+ end
24
+
25
+ Tron.failure :webauth_reset_failed
26
+ end
27
+
28
+ def storage
29
+ request.storage.webauth
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,23 @@
1
+ # module Booth
2
+ # module Userland
3
+ # module Webauths
4
+ # module New
5
+ # class Step
6
+ # include ::Booth::MethodObject
7
+
8
+ # param :authenticator
9
+
10
+ # def call
11
+ # return :register if authenticator.device_id.blank? ||
12
+ # authenticator.public_key.blank? ||
13
+ # authenticator.sign_count.blank?
14
+ # return :choose_nickname if authenticator.nickname.blank?
15
+ # return :confirm if authenticator.confirmed_at.blank?
16
+
17
+ # :completed
18
+ # end
19
+ # end
20
+ # end
21
+ # end
22
+ # end
23
+ # end
@@ -0,0 +1,47 @@
1
+ module Booth
2
+ module Userland
3
+ module Webauths
4
+ module Transitions
5
+ module Sudo
6
+ class AuthenticationInitiation
7
+ include ::Booth::Concerns::Transition
8
+
9
+ def self.applicable?(params:)
10
+ !params&.key?(:webauth)
11
+ end
12
+
13
+ def call
14
+ do_challenge
15
+ end
16
+
17
+ private
18
+
19
+ def do_challenge
20
+ webauth = ::Booth::Webauth::OptionsForGet.call(
21
+ allowed_device_ids: credential.registered_authenticator_ids,
22
+ requires_user_verification: enforce_user_verification?
23
+ )
24
+
25
+ debug { "Remembering sudo challenge #{webauth.challenge.inspect}" }
26
+
27
+ request.sudo.webauthn_challenge = webauth.challenge
28
+ Tron.success :test_challenge_created, public_json: webauth.as_json,
29
+ http_status: :created
30
+ end
31
+
32
+ def credential
33
+ @credential ||= ::Booth::Models::Credential.find(request.authentication.credential_id)
34
+ end
35
+
36
+ def enforce_user_verification?
37
+ ::Booth::Webauth::DemandUserVerification.call(
38
+ credential:,
39
+ force: params[:enforce_user_verification].present?
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,34 @@
1
+ module Booth
2
+ module Userland
3
+ module Webauths
4
+ module Transitions
5
+ module Sudo
6
+ class AuthenticationVerification
7
+ include ::Booth::Concerns::Transition
8
+
9
+ def self.applicable?(params:)
10
+ params.dig(:webauth, :rawId)
11
+ end
12
+
13
+ def call
14
+ request.must_be_post!
15
+ request.must_be_json!
16
+
17
+ do_verify_response
18
+ end
19
+
20
+ private
21
+
22
+ def do_verify_response
23
+ ::Booth::Webauth::AuthenticationVerification.call(
24
+ request:,
25
+ credential_id: request.authentication.credential_id,
26
+ challenge: request.sudo.webauthn_challenge
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,192 @@
1
+ module Booth
2
+ module Userland
3
+ def self.routes(...)
4
+ ::Booth::Routes::Userland.call(...)
5
+ end
6
+
7
+ # -------------
8
+ # Login Actions
9
+ # -------------
10
+
11
+ def self.new_login(...)
12
+ ::Booth::Userland::Logins::New.call(...)
13
+ end
14
+
15
+ def self.create_login(...)
16
+ ::Booth::Userland::Logins::Create.call(...)
17
+ end
18
+
19
+ def self.destroy_login(...)
20
+ ::Booth::Userland::Logins::Destroy.call(...)
21
+ end
22
+
23
+ # ------------------
24
+ # Onboarding Actions
25
+ # ------------------
26
+
27
+ def self.show_onboarding(...)
28
+ ::Booth::Userland::Onboardings::Show.call(...)
29
+ end
30
+
31
+ def self.update_onboarding(...)
32
+ ::Booth::Userland::Onboardings::Update.call(...)
33
+ end
34
+
35
+ # Otp Actions
36
+
37
+ def self.show_otp(...)
38
+ ::Booth::Userland::Otps::Show.call(...)
39
+ end
40
+
41
+ def self.sudo_otp(...)
42
+ ::Booth::Userland::Otps::Sudo.call(...)
43
+ end
44
+
45
+ def self.edit_otp(...)
46
+ ::Booth::Userland::Otps::Edit.call(...)
47
+ end
48
+
49
+ def self.update_otp(...)
50
+ ::Booth::Userland::Otps::Update.call(...)
51
+ end
52
+
53
+ def self.destroy_otp(...)
54
+ ::Booth::Userland::Otps::Destroy.call(...)
55
+ end
56
+
57
+ # Password Actions
58
+
59
+ def self.show_password(...)
60
+ ::Booth::Userland::Passwords::Show.call(...)
61
+ end
62
+
63
+ def self.sudo_password(...)
64
+ ::Booth::Userland::Passwords::Sudo.call(...)
65
+ end
66
+
67
+ def self.edit_password(...)
68
+ ::Booth::Userland::Passwords::Edit.call(...)
69
+ end
70
+
71
+ def self.update_password(...)
72
+ ::Booth::Userland::Passwords::Update.call(...)
73
+ end
74
+
75
+ # GET `remove` is paired with POST `destroy``.
76
+ def self.remove_password(...)
77
+ ::Booth::Userland::Passwords::Remove.call(...)
78
+ end
79
+
80
+ def self.destroy_password(...)
81
+ ::Booth::Userland::Passwords::Destroy.call(...)
82
+ end
83
+
84
+ # Webauth Actions
85
+
86
+ def self.index_webauth(...)
87
+ ::Booth::Userland::Webauths::Index.call(...)
88
+ end
89
+
90
+ def self.sudo_webauth(...)
91
+ ::Booth::Userland::Webauths::Sudo.call(...)
92
+ end
93
+
94
+ def self.new_webauth(...)
95
+ ::Booth::Userland::Webauths::New.call(...)
96
+ end
97
+
98
+ def self.create_webauth(...)
99
+ ::Booth::Userland::Webauths::Create.call(...)
100
+ end
101
+
102
+ def self.destroy_webauth(...)
103
+ ::Booth::Userland::Webauths::Destroy.call(...)
104
+ end
105
+
106
+ # Webauth Helpers
107
+
108
+ # def self.should_add_more_authenticators?(...)
109
+ # ::Booth::Userland::Webauths::ShouldAddMoreAuthenticators.call(...)
110
+ # end
111
+
112
+ # def self.only_one_passwordless_authenticator?(...)
113
+ # index_webauth(...).authenticators.select(&:supports_user_verification?).size == 1
114
+ # end
115
+
116
+ # Personal Contest Actions
117
+
118
+ def self.show_personal_contest(...)
119
+ ::Booth::Userland::PersonalContests::Show.call(...)
120
+ end
121
+
122
+ def self.update_personal_contest(...)
123
+ ::Booth::Userland::PersonalContests::Update.call(...)
124
+ end
125
+
126
+ # Password Reset Actions
127
+
128
+ def self.new_password_reset(...)
129
+ ::Booth::Userland::PasswordResets::New.call(...)
130
+ end
131
+
132
+ def self.create_password_reset(...)
133
+ ::Booth::Userland::PasswordResets::Create.call(...)
134
+ end
135
+
136
+ def self.show_password_reset(...)
137
+ ::Booth::Userland::PasswordResets::Show.call(...)
138
+ end
139
+
140
+ def self.update_password_reset(...)
141
+ ::Booth::Userland::PasswordResets::Update.call(...)
142
+ end
143
+
144
+ # Recovery Actions
145
+
146
+ def self.new_recovery(...)
147
+ ::Booth::Userland::Recoveries::New.call(...)
148
+ end
149
+
150
+ def self.create_recovery(...)
151
+ ::Booth::Userland::Recoveries::Create.call(...)
152
+ end
153
+
154
+ # Registration Actions
155
+
156
+ def self.new_registration(...)
157
+ ::Booth::Userland::Registrations::New.call(...)
158
+ end
159
+
160
+ def self.create_registration(...)
161
+ ::Booth::Userland::Registrations::Create.call(...)
162
+ end
163
+
164
+ # Session Actions
165
+
166
+ def self.index_sessions(...)
167
+ ::Booth::Userland::Sessions::Index.call(...)
168
+ end
169
+
170
+ def self.other_sessions?(...)
171
+ index_sessions(...).reject(&:is_current).any?
172
+ end
173
+
174
+ def self.show_session(...)
175
+ ::Booth::Userland::Sessions::Show.call(...)
176
+ end
177
+
178
+ def self.destroy_session(...)
179
+ ::Booth::Userland::Sessions::DestroyOneOrOther.call(...)
180
+ end
181
+
182
+ def self.destroy_all_other_sessions(...)
183
+ ::Booth::Userland::Sessions::DestroyOneOrOther.call(...)
184
+ end
185
+
186
+ # General Helpers
187
+
188
+ def self.extract_flash_messages(...)
189
+ ::Booth::Userland::ExtractFlashMessages.call(...)
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,3 @@
1
+ module Booth
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,68 @@
1
+ module Booth
2
+ module Webauth
3
+ class AuthenticationVerification
4
+ include ::Booth::MethodObject
5
+ include ::Booth::Logging
6
+
7
+ option :request
8
+ option :credential_id
9
+ option :challenge
10
+
11
+ def call
12
+ raise 'this authenticator doesnt match the credential' if credential_id != authenticator.credential_id
13
+
14
+ debug do
15
+ "Verifying using challenge #{challenge.inspect} and public key #{authenticator.public_key.inspect} and sign count #{authenticator.sign_count.inspect}"
16
+ end
17
+ webauth.verify(
18
+ challenge,
19
+ public_key: authenticator.public_key,
20
+ sign_count: authenticator.sign_count
21
+ )
22
+ debug { 'Response successfully verified' }
23
+
24
+ authenticator.update!(sign_count: webauth.sign_count)
25
+ sudo.webauth!
26
+
27
+ Tron.success :webauth_authentication_verification_successful,
28
+ credential: authenticator.credential,
29
+ public_json: {},
30
+ http_status: :created
31
+ rescue WebAuthn::SignCountVerificationError => e
32
+ # TODO
33
+ raise 'implement me, counter differed, not too bad?'
34
+ rescue WebAuthn::Error => e
35
+ debug { "Response verification failed: #{e.message}" }
36
+ # TODO: Audit but don't throttle?
37
+ Tron.failure :webauth_failed, public_json: {},
38
+ public_message: "Verification failed: #{e.message}",
39
+ http_status: :unprocessable_entity
40
+ rescue RuntimeError => e
41
+ # This happens e.g. if the param[:id] does not match the param[:rawId]
42
+ raise
43
+ ensure
44
+ sudo.webauthn_challenge = nil
45
+ end
46
+
47
+ private
48
+
49
+ delegate :sudo, to: :request
50
+
51
+ def webauth
52
+ # The route `/things/123` overrides the `param[:id]` with 123.
53
+ # But thankfully, webauth also sends the rawId, which is identical to the id.
54
+ # So we override `param[:id]` back to what the webauth JS client sent in.
55
+ # If we don't do this, the webauth ruby library will raise a RuntimeError,
56
+ # complaining that the `id` does not match the `rawId`.
57
+ params_with_correct_id = request.params.dup.merge(id: request.params[:rawId])
58
+ ::WebAuthn::Credential.from_get(params_with_correct_id)
59
+ end
60
+
61
+ def authenticator
62
+ device_id = Base64.strict_encode64(webauth.raw_id)
63
+ @authenticator ||= ::Booth::Models::Authenticator.where(credential_id:)
64
+ .find_by!(device_id:)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,29 @@
1
+ module Booth
2
+ module Webauth
3
+ class DemandUserVerification
4
+ include ::Booth::MethodObject
5
+
6
+ option :credential
7
+ option :force, default: -> { false }
8
+
9
+ def call
10
+ !!why?
11
+ end
12
+
13
+ private
14
+
15
+ def why?
16
+ return :forced if force.present?
17
+
18
+ # From scratch, you only have a username.
19
+ # Adding webauth in that situation would mean passwordless login.
20
+ return :first_time_passwordless if credential.mode_first_time?
21
+
22
+ # Passwordless always requires verification
23
+ return :passwordless if credential.mode_username_and_webauth?
24
+
25
+ false
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,46 @@
1
+ module Booth
2
+ module Webauth
3
+ class OptionsForCreate
4
+ include ::Booth::MethodObject
5
+
6
+ option :webauthn_id
7
+ option :username
8
+ option :requires_user_verification
9
+ option :device_ids_to_exclude, default: -> {}
10
+
11
+ def call
12
+ verify_setup
13
+
14
+ ::WebAuthn::Credential.options_for_create(
15
+ user: {
16
+ id: webauthn_id,
17
+ name: username
18
+ # Also supports `display_name: "..."`
19
+ },
20
+ authenticator_selection: { user_verification: },
21
+ # Use the following instead, if you want to force device-internal authenticators and forbid USB etc.
22
+ # authenticator_selection: { user_verification:, authenticator_attachment: 'platform' },
23
+ exclude: device_ids_to_exclude
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ # See https://caniuse.com/mdn-api_publickeycredentialrequestoptions_userverification
30
+ # Can be turned off with `:discouraged`
31
+ def user_verification
32
+ requires_user_verification ? :required : :discouraged
33
+ end
34
+
35
+ def requires_user_verification?
36
+ !!requires_user_verification
37
+ end
38
+
39
+ def verify_setup
40
+ return if WebAuthn.configuration.rp_name.present?
41
+
42
+ raise ::Booth::Errors::MissingRelyingParty
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,29 @@
1
+ module Booth
2
+ module Webauth
3
+ class OptionsForGet
4
+ include ::Booth::MethodObject
5
+
6
+ option :allowed_device_ids
7
+ option :requires_user_verification
8
+
9
+ def call
10
+ WebAuthn::Credential.options_for_get(
11
+ allow: allowed_device_ids,
12
+ user_verification: user_verification_value
13
+ )
14
+ end
15
+
16
+ private
17
+
18
+ # See https://caniuse.com/mdn-api_publickeycredentialrequestoptions_userverification
19
+ # Can be turned off with `:discouraged`
20
+ def user_verification_value
21
+ requires_user_verification ? :required : :discouraged
22
+ end
23
+
24
+ def requires_user_verification?
25
+ !!requires_user_verification
26
+ end
27
+ end
28
+ end
29
+ end