booth 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,162 @@
1
+ /// <reference path='webauthn-json.ts' />
2
+ //= require rails-ujs
3
+ //= require @github/webauthn-json/dist/browser-global/webauthn-json.browser-global
4
+ var Booth;
5
+ (function (Booth) {
6
+ function reloadSoon() {
7
+ const seconds = Math.floor(Math.random() * 3) + 5; // 4,5,6 or 7 to avoid request bursts
8
+ setTimeout(() => {
9
+ console.debug('Reloading page...');
10
+ window.location.reload();
11
+ }, 1000 * seconds);
12
+ }
13
+ Booth.reloadSoon = reloadSoon;
14
+ let Webauth;
15
+ (function (Webauth) {
16
+ class Registration {
17
+ constructor(form, challengeData) {
18
+ this.form = form;
19
+ this.challengeData = challengeData;
20
+ this.call();
21
+ }
22
+ call() {
23
+ console.debug('WebAuthn Registration Ceremony - Initiation Phase Part 2 - START');
24
+ webauthnJSON.create({ publicKey: this.challengeData }).then((responseData) => {
25
+ console.debug(`WebAuthn Registration Ceremony - Initiation Phase Part 2 - SUCCESS - ${JSON.stringify(responseData)}`);
26
+ new Verification(this.form, responseData);
27
+ }).catch((error) => {
28
+ console.error(`WebAuthn Registration Ceremony - Verification Phase - ERROR - ${error.name} - ${error.message}`);
29
+ if (error.name == 'NotAllowedError') {
30
+ // Every webauth registration form needs this localized error message.
31
+ alert(this.form.dataset.boothIncompatibleDeviceMessage);
32
+ }
33
+ else {
34
+ alert(error.message);
35
+ }
36
+ });
37
+ }
38
+ }
39
+ Webauth.Registration = Registration;
40
+ class Authentication {
41
+ constructor(form, challengeData) {
42
+ this.form = form;
43
+ this.challengeData = challengeData;
44
+ this.call();
45
+ }
46
+ call() {
47
+ console.debug('WebAuthn Authentication Ceremony - Initiation Phase Part 2 - START');
48
+ webauthnJSON.get({ publicKey: this.challengeData }).then((responseData) => {
49
+ console.debug(`WebAuthn Authentication Ceremony - Initiation Phase Part 2 - SUCCESS - ${JSON.stringify(responseData)}`);
50
+ new Verification(this.form, responseData);
51
+ }).catch((error) => {
52
+ console.error(`WebAuthn Authentication Ceremony - Verification Phase - ERROR - ${error.name} - ${error.message}`);
53
+ if (error.name == 'NotAllowedError') {
54
+ // Every webauth authentication form needs this localized error message.
55
+ alert(this.form.dataset.boothUnknownDeviceMessage);
56
+ }
57
+ else {
58
+ alert(error.message);
59
+ }
60
+ });
61
+ }
62
+ }
63
+ Webauth.Authentication = Authentication;
64
+ class Verification {
65
+ constructor(form, webauth) {
66
+ this.form = form;
67
+ this.webauth = webauth;
68
+ this.call();
69
+ }
70
+ call() {
71
+ console.debug(`WebAuthn Ceremony - Verification Phase - START - ${this.formMethod} ${this.formUrl}`);
72
+ const options = {
73
+ body: JSON.stringify(this.webauth),
74
+ method: this.formMethod,
75
+ credentials: 'same-origin',
76
+ // Rails should not redirect us anywhere when making API calls.
77
+ // But in order to avoid mistakes let's be sure and never follow anywhere.
78
+ redirect: 'manual',
79
+ headers: {
80
+ 'Content-type': 'application/json',
81
+ 'Accept': 'application/json',
82
+ 'X-CSRF-Token': this.csrfToken
83
+ }
84
+ };
85
+ fetch(this.formUrl, options)
86
+ .then((response) => {
87
+ // Conventionally the server returns 201 on successful WebAuth verification.
88
+ // That makes it easier to detect a mistake (most other server responses will usually return 200).
89
+ if (response.status == 201) {
90
+ console.debug(`WebAuthn Ceremony - Verification Phase - SUCCESS - ${response.statusText}`);
91
+ console.info(`Reloading page with a GET request: ${window.location.href}`);
92
+ window.location.href = window.location.href;
93
+ }
94
+ else {
95
+ // TODO: Extract error message from JSON that the server sent.
96
+ const message = `WebAuthn Ceremony - Verification Phase - ERROR - ${response.statusText} ${response.body}`;
97
+ console.debug(message);
98
+ alert(message);
99
+ }
100
+ }).catch((error) => {
101
+ const message = 'Please check your Internet connection and try again later.';
102
+ console.error(message);
103
+ alert(message);
104
+ });
105
+ }
106
+ get formUrl() {
107
+ return this.form.action;
108
+ }
109
+ get formMethod() {
110
+ return this.form.querySelector('input[name="_method"]')?.value?.toUpperCase() || 'POST';
111
+ }
112
+ get csrfToken() {
113
+ return document.querySelector('meta[name="csrf-token"]')?.content;
114
+ }
115
+ }
116
+ Webauth.Verification = Verification;
117
+ })(Webauth = Booth.Webauth || (Booth.Webauth = {}));
118
+ })(Booth || (Booth = {}));
119
+ // --------------
120
+ // Initialization
121
+ // --------------
122
+ document.addEventListener('DOMContentLoaded', function () {
123
+ document.querySelectorAll('.js-booth-webauth-registration').forEach((form) => {
124
+ form.addEventListener('ajax:before', () => {
125
+ console.debug('WebAuthn Registration Ceremony - Initiation Phase Part 1 - START');
126
+ });
127
+ form.addEventListener('ajax:success', (event) => {
128
+ const [data, status, xhr] = event.detail;
129
+ console.debug(`WebAuthn Registration Ceremony - Initiation Phase Part 1 - SUCCESS - ${JSON.stringify(data)}`);
130
+ new Booth.Webauth.Registration(form, data);
131
+ });
132
+ form.addEventListener('ajax:error', (event) => {
133
+ const [data, status, xhr] = event.detail;
134
+ // TODO: Extract error message from JSON that the server sent.
135
+ // I don't think we need to reload the page on the registration page.
136
+ const message = `WebAuthn Registration Ceremony - Initiation Phase Part 1 - ERROR - ${xhr.responseText}`;
137
+ console.error(message);
138
+ alert(message);
139
+ });
140
+ });
141
+ document.querySelectorAll('.js-booth-webauth-authentication').forEach((form) => {
142
+ form.addEventListener('ajax:before', () => {
143
+ console.debug('WebAuthn Authentication Ceremony - Initiation Phase Part 1 - START');
144
+ });
145
+ form.addEventListener('ajax:success', (event) => {
146
+ const [data, status, xhr] = event.detail;
147
+ console.debug(`WebAuthn Authentication Ceremony - Initiation Phase Part 1 - SUCCESS - ${JSON.stringify(data)}`);
148
+ new Booth.Webauth.Authentication(form, data);
149
+ });
150
+ form.addEventListener('ajax:error', (event) => {
151
+ const [data, status, xhr] = event.detail;
152
+ // TODO: Extract error message from JSON that the server sent.
153
+ // On the authentication page there are timeout restrictions, it would be a good idea to reload page.
154
+ const message = `WebAuthn Authentication Ceremony - Initiation Phase Part 1 - ERROR - ${xhr.responseText}`;
155
+ console.error(message);
156
+ alert(message);
157
+ });
158
+ });
159
+ if (document.querySelector('.js-booth-polling'))
160
+ Booth.reloadSoon();
161
+ });
162
+ //# sourceMappingURL=all.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"all.js","sourceRoot":"","sources":["webauthn-json.ts","booth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,194 @@
1
+ /// <reference path='webauthn-json.ts' />
2
+
3
+ //= require rails-ujs
4
+ //= require @github/webauthn-json/dist/browser-global/webauthn-json.browser-global
5
+
6
+ namespace Booth {
7
+ export function reloadSoon() {
8
+ const seconds = Math.floor(Math.random() * 3) + 5 // 4,5,6 or 7 to avoid request bursts
9
+
10
+ setTimeout(() => {
11
+ console.debug('Reloading page...')
12
+ window.location.reload()
13
+ }, 1000 * seconds)
14
+ }
15
+
16
+ export namespace Webauth {
17
+ export class Registration {
18
+ private form: HTMLFormElement
19
+ private challengeData: any
20
+
21
+ constructor(form: HTMLFormElement, challengeData: any) {
22
+ this.form = form
23
+ this.challengeData = challengeData
24
+ this.call()
25
+ }
26
+
27
+ private call() {
28
+ console.debug('WebAuthn Registration Ceremony - Initiation Phase Part 2 - START')
29
+ webauthnJSON.create({ publicKey: this.challengeData }).then((responseData) => {
30
+ console.debug(`WebAuthn Registration Ceremony - Initiation Phase Part 2 - SUCCESS - ${JSON.stringify(responseData)}`)
31
+ new Verification(this.form, responseData)
32
+
33
+ }).catch((error: DOMException) => {
34
+ console.error(`WebAuthn Registration Ceremony - Verification Phase - ERROR - ${error.name} - ${error.message}`)
35
+
36
+ if (error.name == 'NotAllowedError') {
37
+ // Every webauth registration form needs this localized error message.
38
+ alert(this.form.dataset.boothIncompatibleDeviceMessage)
39
+ } else {
40
+ alert(error.message)
41
+ }
42
+ })
43
+ }
44
+ }
45
+
46
+ export class Authentication {
47
+ private form: HTMLFormElement
48
+ private challengeData: any
49
+
50
+ constructor(form: HTMLFormElement, challengeData: any) {
51
+ this.form = form
52
+ this.challengeData = challengeData
53
+ this.call()
54
+ }
55
+
56
+ private call() {
57
+ console.debug('WebAuthn Authentication Ceremony - Initiation Phase Part 2 - START')
58
+ webauthnJSON.get({ publicKey: this.challengeData }).then((responseData) => {
59
+ console.debug(`WebAuthn Authentication Ceremony - Initiation Phase Part 2 - SUCCESS - ${JSON.stringify(responseData)}`)
60
+ new Verification(this.form, responseData)
61
+
62
+ }).catch((error) => {
63
+ console.error(`WebAuthn Authentication Ceremony - Verification Phase - ERROR - ${error.name} - ${error.message}`)
64
+
65
+ if (error.name == 'NotAllowedError') {
66
+ // Every webauth authentication form needs this localized error message.
67
+ alert(this.form.dataset.boothUnknownDeviceMessage)
68
+ } else {
69
+ alert(error.message)
70
+ }
71
+ })
72
+ }
73
+ }
74
+
75
+ export class Verification {
76
+ private form: HTMLFormElement
77
+ private webauth: any
78
+
79
+ public constructor(form: HTMLFormElement, webauth: any) {
80
+ this.form = form
81
+ this.webauth = webauth
82
+ this.call()
83
+ }
84
+
85
+ private call() {
86
+ console.debug(`WebAuthn Ceremony - Verification Phase - START - ${this.formMethod} ${this.formUrl}`)
87
+
88
+ const options: RequestInit = {
89
+ body: JSON.stringify(this.webauth),
90
+ method: this.formMethod,
91
+ credentials: 'same-origin',
92
+ // Rails should not redirect us anywhere when making API calls.
93
+ // But in order to avoid mistakes let's be sure and never follow anywhere.
94
+ redirect: 'manual',
95
+ headers: {
96
+ 'Content-type': 'application/json',
97
+ 'Accept': 'application/json',
98
+ 'X-CSRF-Token': this.csrfToken
99
+ }
100
+ }
101
+
102
+ fetch(this.formUrl, options)
103
+ .then((response) => {
104
+
105
+ // Conventionally the server returns 201 on successful WebAuth verification.
106
+ // That makes it easier to detect a mistake (most other server responses will usually return 200).
107
+ if (response.status == 201) {
108
+ console.debug(`WebAuthn Ceremony - Verification Phase - SUCCESS - ${response.statusText}`)
109
+ console.info(`Reloading page with a GET request: ${window.location.href}`)
110
+ window.location.href = window.location.href
111
+
112
+ } else {
113
+ // TODO: Extract error message from JSON that the server sent.
114
+ const message = `WebAuthn Ceremony - Verification Phase - ERROR - ${response.statusText} ${response.body}`
115
+ console.debug(message)
116
+ alert(message)
117
+ }
118
+
119
+ }).catch((error) => {
120
+ const message = 'Please check your Internet connection and try again later.'
121
+ console.error(message)
122
+ alert(message)
123
+ })
124
+ }
125
+
126
+ private get formUrl() {
127
+ return this.form.action
128
+ }
129
+
130
+ private get formMethod() {
131
+ return this.form.querySelector<HTMLInputElement>('input[name="_method"]')?.value?.toUpperCase() || 'POST'
132
+ }
133
+
134
+ private get csrfToken() {
135
+ return document.querySelector<HTMLMetaElement>('meta[name="csrf-token"]')?.content
136
+ }
137
+ }
138
+ }
139
+ }
140
+
141
+
142
+
143
+
144
+ // --------------
145
+ // Initialization
146
+ // --------------
147
+
148
+ document.addEventListener('DOMContentLoaded', function() {
149
+
150
+ document.querySelectorAll('.js-booth-webauth-registration').forEach((form: HTMLFormElement) => {
151
+ form.addEventListener('ajax:before', () => {
152
+ console.debug('WebAuthn Registration Ceremony - Initiation Phase Part 1 - START')
153
+ })
154
+
155
+ form.addEventListener('ajax:success', (event: CustomEvent) => {
156
+ const [data, status, xhr] = event.detail
157
+ console.debug(`WebAuthn Registration Ceremony - Initiation Phase Part 1 - SUCCESS - ${JSON.stringify(data)}`)
158
+ new Booth.Webauth.Registration(form, data)
159
+ })
160
+
161
+ form.addEventListener('ajax:error', (event: CustomEvent) => {
162
+ const [data, status, xhr] = event.detail
163
+ // TODO: Extract error message from JSON that the server sent.
164
+ // I don't think we need to reload the page on the registration page.
165
+ const message = `WebAuthn Registration Ceremony - Initiation Phase Part 1 - ERROR - ${xhr.responseText}`
166
+ console.error(message)
167
+ alert(message)
168
+ })
169
+ })
170
+
171
+ document.querySelectorAll('.js-booth-webauth-authentication').forEach((form: HTMLFormElement) => {
172
+ form.addEventListener('ajax:before', () => {
173
+ console.debug('WebAuthn Authentication Ceremony - Initiation Phase Part 1 - START')
174
+ })
175
+
176
+ form.addEventListener('ajax:success', (event: CustomEvent) => {
177
+ const [data, status, xhr] = event.detail
178
+ console.debug(`WebAuthn Authentication Ceremony - Initiation Phase Part 1 - SUCCESS - ${JSON.stringify(data)}`)
179
+ new Booth.Webauth.Authentication(form, data)
180
+ })
181
+
182
+ form.addEventListener('ajax:error', (event: CustomEvent) => {
183
+ const [data, status, xhr] = event.detail
184
+ // TODO: Extract error message from JSON that the server sent.
185
+ // On the authentication page there are timeout restrictions, it would be a good idea to reload page.
186
+ const message = `WebAuthn Authentication Ceremony - Initiation Phase Part 1 - ERROR - ${xhr.responseText}`
187
+ console.error(message)
188
+ alert(message)
189
+ })
190
+ })
191
+
192
+ if (document.querySelector('.js-booth-polling')) Booth.reloadSoon()
193
+
194
+ })
@@ -0,0 +1,99 @@
1
+ type Base64urlString = string;
2
+ type SchemaLeaf = "copy" | "convert";
3
+ interface SchemaObject {
4
+ [property: string]: {
5
+ required: boolean;
6
+ schema: Schema;
7
+ };
8
+ }
9
+ type SchemaArray = [SchemaObject] | [SchemaLeaf];
10
+ type Schema = SchemaLeaf | SchemaArray | SchemaObject;
11
+ interface CredPropsAuthenticationExtensionsClientOutputsJSON {
12
+ rk: boolean;
13
+ }
14
+ interface PublicKeyCredentialDescriptorJSON {
15
+ type: PublicKeyCredentialType;
16
+ id: Base64urlString;
17
+ transports?: AuthenticatorTransport[];
18
+ }
19
+ interface SimpleWebAuthnExtensionsJSON {
20
+ appid?: string;
21
+ appidExclude?: string;
22
+ credProps?: boolean;
23
+ }
24
+ interface SimpleClientExtensionResultsJSON {
25
+ appid?: boolean;
26
+ appidExclude?: boolean;
27
+ credProps?: CredPropsAuthenticationExtensionsClientOutputsJSON;
28
+ }
29
+ interface PublicKeyCredentialUserEntityJSON extends PublicKeyCredentialEntity {
30
+ displayName: string;
31
+ id: Base64urlString;
32
+ }
33
+ declare interface AuthenticatorSelectionCriteriaJSON extends AuthenticatorSelectionCriteria {
34
+ residentKey?: ResidentKeyRequirement;
35
+ }
36
+ interface PublicKeyCredentialCreationOptionsJSON {
37
+ rp: PublicKeyCredentialRpEntity;
38
+ user: PublicKeyCredentialUserEntityJSON;
39
+ challenge: Base64urlString;
40
+ pubKeyCredParams: PublicKeyCredentialParameters[];
41
+ timeout?: number;
42
+ excludeCredentials?: PublicKeyCredentialDescriptorJSON[];
43
+ authenticatorSelection?: AuthenticatorSelectionCriteriaJSON;
44
+ attestation?: AttestationConveyancePreference;
45
+ extensions?: SimpleWebAuthnExtensionsJSON;
46
+ }
47
+ declare interface CredentialCreationOptionsJSON {
48
+ publicKey: PublicKeyCredentialCreationOptionsJSON;
49
+ signal?: AbortSignal;
50
+ }
51
+ interface AuthenticatorAttestationResponseJSON {
52
+ clientDataJSON: Base64urlString;
53
+ attestationObject: Base64urlString;
54
+ }
55
+ declare interface PublicKeyCredentialWithAttestationJSON {
56
+ id: string;
57
+ type: PublicKeyCredentialType;
58
+ rawId: Base64urlString;
59
+ response: AuthenticatorAttestationResponseJSON;
60
+ clientExtensionResults: SimpleClientExtensionResultsJSON;
61
+ }
62
+ interface PublicKeyCredentialRequestOptionsJSON {
63
+ challenge: Base64urlString;
64
+ timeout?: number;
65
+ rpId?: string;
66
+ allowCredentials?: PublicKeyCredentialDescriptorJSON[];
67
+ userVerification?: UserVerificationRequirement;
68
+ extensions?: SimpleWebAuthnExtensionsJSON;
69
+ }
70
+ declare interface CredentialRequestOptionsJSON {
71
+ mediation?: CredentialMediationRequirement;
72
+ publicKey?: PublicKeyCredentialRequestOptionsJSON;
73
+ signal?: AbortSignal;
74
+ }
75
+ interface AuthenticatorAssertionResponseJSON {
76
+ clientDataJSON: Base64urlString;
77
+ authenticatorData: Base64urlString;
78
+ signature: Base64urlString;
79
+ userHandle: Base64urlString | null;
80
+ }
81
+ declare interface PublicKeyCredentialWithAssertionJSON {
82
+ type: PublicKeyCredentialType;
83
+ id: string;
84
+ rawId: Base64urlString;
85
+ response: AuthenticatorAssertionResponseJSON;
86
+ clientExtensionResults: SimpleClientExtensionResultsJSON;
87
+ }
88
+ declare const schema: {
89
+ [s: string]: Schema;
90
+ };
91
+ // export function create(requestJSON: CredentialCreationOptionsJSON): Promise<PublicKeyCredentialWithAttestationJSON>;
92
+ // export function get(requestJSON: CredentialRequestOptionsJSON): Promise<PublicKeyCredentialWithAssertionJSON>;
93
+ // export function supported(): boolean;
94
+
95
+ declare class webauthnJSON {
96
+ static create(requestJSON: CredentialCreationOptionsJSON): Promise<PublicKeyCredentialWithAttestationJSON>;
97
+ static get(requestJSON: CredentialRequestOptionsJSON): Promise<PublicKeyCredentialWithAssertionJSON>;
98
+ static supported(): boolean;
99
+ }
@@ -0,0 +1,84 @@
1
+ de:
2
+
3
+ activerecord:
4
+ attributes:
5
+ booth/models/onboarding:
6
+ password: Passwort
7
+ password_confirmation: Passwort
8
+ authenticator_nickname: Gerätename
9
+ errors:
10
+ messages:
11
+ not_pwned: ist unsicher, weil es in %{count} Datenlecks veröffentlicht wurde.
12
+
13
+ booth:
14
+ all_other_sessions_revoked: All other devices except this one have been logged out.
15
+ already_responded_to_contest: You have already responded to this contest.
16
+ attempt_was_ignored: Dieser Versuch wurde ignoriert.
17
+ attempts_left: You may try %{attempts_left} more times.
18
+ authenticator_not_found: Could not find that hardware key.
19
+ authenticator_removal_failed: Could not delete that hardware key.
20
+ blank_contest_code: You did not enter a code.
21
+ blank_email: Please provide an email address.
22
+ blank_nickname: Please provide a name for your device.
23
+ blank_otp: Bitte gib den Einmalcode ein, den dir deine Authenticator App anzeigt.
24
+ blank_password: Bitte gib dein Passwort ein.
25
+ blank_secret_key: The provided secret key is empty. Is the URL correct?
26
+ blank_username: Trage bitte deinen Benutzernamen ein.
27
+ contest_response_accepted: You entered the correct code. You have now been logged in on the other device.
28
+ contest_timed_out: You had %{lifespan_minutes} minutes to enter the response code, but it took too long. Please start again.
29
+ email_too_long: The provided email is too long. It must be less at most %{maximum} characters long.
30
+ email_too_short: The provided email is too short. It should be at least %{minimum} characters long.
31
+ incompatible_webauth_device: Sorry, this device cannot be used. Please try another webauth device.
32
+ invalid_contest_code_format: Der Code darf nur aus Zahlen bestehen.
33
+ invalid_email_characters: The provided email address contains invalid characters.
34
+ invalid_email_format: The provided email address does not look valid.
35
+ invalid_otp_format: Der Einmalcode darf nur aus Zahlen bestehen.
36
+ invalid_secret_key_format: The provided secret key contains invalid characters. It should be from the Base58 alphabet.
37
+ invalid_username_format: Der eingegebene Benutzername enthält ungültige Zeichen.
38
+ last_attempt: This is your last attempt and you will have to contact customer service if you fail.
39
+ logged_in_user_cannot_reset_password: You are currently logged in. If you forgot your password, try another browser or logout first.
40
+ login_timeout: Du hattest %{lifespan_minutes} Minuten um den Login abzuschließen, es hat aber länger gedauert. Bitte beginne erneut.
41
+ missing_secret_key: Missing the secret key parameter. Is the URL correct?
42
+ mode_username_and_password_description: Benutzername und Passwort. Kann leicht gestohlen werden.
43
+ mode_username_and_password_title: Passwort (unsicher)
44
+ mode_username_and_webauth_description: Auch bekannt als WebAuthentication (WebAuthn), FIDO2, CTAP2, Apple Passkey (Touch ID, Face ID).
45
+ mode_username_and_webauth_title: Hardwareschlüssel (empfohlen)
46
+ mode_username_password_and_otp_description: Wenn kein kompatibler Hardwareschlüssel zur Hand ist.
47
+ mode_username_password_and_otp_title: Passwort und Einmalpasswort
48
+ mode_username_password_and_webauth_description: Zusätzlich zum Hardwareschlüssel muss jedesmal auch das Passwort eingegeben werden.
49
+ mode_username_password_and_webauth_title: Passwort und Hardwareschlüssel (am sichersten)
50
+ otp_removed: You successfully removed your OTP configuration.
51
+ otp_sudo_timeout: You had %{lifespan_minutes} minutes to perform this action, but it took too long. Please start again by providing your current OTP again.
52
+ otp_unavailable: You can't use OTP at this time.
53
+ password_changed: You successfully changed your password.
54
+ password_removed: You successfully removed your password.
55
+ password_reset_needs_username: To reset your password, please provide a username first.
56
+ password_reset_not_available: This username cannot reset the password. Please contact support.
57
+ password_successfully_reset: Your new password has been saved.
58
+ password_sudo_timeout: You had %{lifespan_minutes} minutes to perform this action, but it took too long. Please start again by providing your current password again.
59
+ password_unavailable: You can't use a password at this time.
60
+ passwordless_cannot_change_password: You cannot change your password because you don't have one.
61
+ permanently_blocked: You failed too many times for this account. Please contact customer service.
62
+ session_revoked: Das Gerät mit der IP %{ip} wurde erfolgreich ausgeloggt.
63
+ short_password: Das kann nicht dein Passwort sein, weil es zu kurz ist. Es muss aus mindestens %{minlength} Zeichen bestehen.
64
+ some_authenticator_removed: Hardware key successfully deleted.
65
+ successfully_logged_out: Du hast dich erfolgreich ausgeloggt.
66
+ try_again_cooldown: Du kannst es in %{distance_of_time_until_cooldown} erneut probieren.
67
+ uninitialized_credential: This account has not been initialized yet. Please contact support.
68
+ unknown_email: We don't know that email.
69
+ unknown_secret_key: The provided secret key is unknown.
70
+ unknown_username: Dieser Benutzername existiert nicht.
71
+ unknown_webauth_device: Sorry, We don't recognize that device. Have you registered it before?
72
+ username_already_exists: This username already exists. Try to login instead?
73
+ username_too_long: The provided username is too long. It must be less at most %{maximum} characters long.
74
+ username_too_short: The provided username is too short. It should be at least %{minimum} characters long.
75
+ webauth_irremovable: You cannot remove any of your hardware keys.
76
+ webauth_missing_authenticators: You cannot login using webauth, because we don't know any of your hardware keys.
77
+ webauth_removed: Hardware keys successfully deleted. Now you log in only with your password.
78
+ webauth_unavailable: You can't use WebAuthn at this time.
79
+ wrong_contest_code_length: The code you entered was not %{digits} digits long.
80
+ wrong_otp_length: Der Einmalcode muss aus %{digits} Zahlen bestehen.
81
+ wrong_otp: Dieser Einmalcode ist falsch, vielleicht ist er schon abgelaufen.
82
+ wrong_password: Das war nicht das richtige Passwort.
83
+ wrong_response_code: This was not the code that is currently shown on the login page on the other device.
84
+ wrong_secret_key_length: The provided secret key does not have the correct length. It should be 30 characters long.
@@ -0,0 +1,79 @@
1
+ en:
2
+
3
+ activerecord:
4
+ errors:
5
+ messages:
6
+ not_pwned: is isecure, because it has been published in %{count} data leaks.
7
+
8
+ booth:
9
+ all_other_sessions_revoked: All other devices except this one have been logged out.
10
+ already_responded_to_contest: You have already responded to this contest.
11
+ attempt_was_ignored: This attempt has been ignored.
12
+ attempts_left: You may try %{attempts_left} more times.
13
+ authenticator_not_found: Could not find that hardware key.
14
+ authenticator_removal_failed: Could not delete that hardware key.
15
+ blank_contest_code: You did not enter a code.
16
+ blank_email: Please provide an email address.
17
+ blank_nickname: Please provide a name for your device.
18
+ blank_otp: Please provide the one-time code your authenticator app is showing you.
19
+ blank_password: Please provide a password.
20
+ blank_secret_key: The provided secret key is empty. Is the URL correct?
21
+ blank_username: Please provide a username.
22
+ contest_response_accepted: You entered the correct code. You have now been logged in on the other device.
23
+ contest_timed_out: You had %{lifespan_minutes} minutes to enter the response code, but it took too long. Please start again.
24
+ email_too_long: The provided email is too long. It must be less at most %{maximum} characters long.
25
+ email_too_short: The provided email is too short. It should be at least %{minimum} characters long.
26
+ incompatible_webauth_device: Sorry, this device cannot be used. Please try another webauth device.
27
+ invalid_contest_code_format: The code may only consist of digits.
28
+ invalid_email_characters: The provided email address contains invalid characters.
29
+ invalid_email_format: The provided email address does not look valid.
30
+ invalid_otp_format: The one-time may only consist of digits.
31
+ invalid_secret_key_format: The provided secret key contains invalid characters. It should be from the Base58 alphabet.
32
+ invalid_username_format: The provided username contains invalid characters.
33
+ last_attempt: This is your last attempt and you will have to contact customer service if you fail.
34
+ logged_in_user_cannot_reset_password: You are currently logged in. If you forgot your password, try another browser or logout first.
35
+ login_timeout: You had %{lifespan_minutes} minutes to complete the login, but it took too long. Please start again.
36
+ missing_secret_key: Missing the secret key parameter. Is the URL correct?
37
+ mode_username_and_password_description: Username and password. Can easily be stolen.
38
+ mode_username_and_password_title: Password (insecure)
39
+ mode_username_and_webauth_description: Also known as WebAuthentication (WebAuthn), FIDO2, CTAP2, Apple Passkey (Touch ID, Face ID).
40
+ mode_username_and_webauth_title: Hardware key (recommended)
41
+ mode_username_password_and_otp_description: Use this if you don't have a hardware key.
42
+ mode_username_password_and_otp_title: Password and one-time-password.
43
+ mode_username_password_and_webauth_description: Additionally to your hardware key, you will have to enter your password. So nobody with your hardware key can just log in.
44
+ mode_username_password_and_webauth_title: Password and hardware key (most secure)
45
+ otp_removed: You successfully removed your OTP configuration.
46
+ otp_sudo_timeout: You had %{lifespan_minutes} minutes to perform this action, but it took too long. Please start again by providing your current OTP again.
47
+ otp_unavailable: You can't use OTP at this time.
48
+ password_changed: You successfully changed your password.
49
+ password_removed: You successfully removed your password.
50
+ password_reset_needs_username: To reset your password, please provide a username first.
51
+ password_reset_not_available: This username cannot reset the password. Please contact support.
52
+ password_successfully_reset: Your new password has been saved.
53
+ password_sudo_timeout: You had %{lifespan_minutes} minutes to perform this action, but it took too long. Please start again by providing your current password again.
54
+ password_unavailable: You can't use a password at this time.
55
+ passwordless_cannot_change_password: You cannot change your password because you don't have one.
56
+ permanently_blocked: You failed too many times for this account. Please contact customer service.
57
+ session_revoked: The logged in device with the IP %{ip} has successfully been logged out.
58
+ short_password: That cannot be the password, because it was too short. It must be at least %{minlength} characters long.
59
+ some_authenticator_removed: Hardware key successfully deleted.
60
+ successfully_logged_out: Logout successful.
61
+ try_again_cooldown: You may try again in %{distance_of_time_until_cooldown}.
62
+ uninitialized_credential: This account has not been initialized yet. Please contact support.
63
+ unknown_email: We don't know that email.
64
+ unknown_secret_key: The provided secret key is unknown.
65
+ unknown_username: We don't know that username.
66
+ unknown_webauth_device: Sorry, We don't recognize that device. Have you registered it before?
67
+ username_already_exists: This username already exists. Try to login instead?
68
+ username_too_long: The provided username is too long. It must be less at most %{maximum} characters long.
69
+ username_too_short: The provided username is too short. It should be at least %{minimum} characters long.
70
+ webauth_irremovable: You cannot remove any of your hardware keys.
71
+ webauth_missing_authenticators: You cannot login using webauth, because we don't know any of your hardware keys.
72
+ webauth_removed: Hardware keys successfully deleted. Now you log in only with your password.
73
+ webauth_unavailable: You can't use WebAuthn at this time.
74
+ wrong_contest_code_length: The code you entered was not %{digits} digits long.
75
+ wrong_otp_length: The one-time code must be %{digits} digits long.
76
+ wrong_otp: The one-time code is wrong, maybe it already expired.
77
+ wrong_password: Wrong password.
78
+ wrong_response_code: This was not the code that is currently shown on the login page on the other device.
79
+ wrong_secret_key_length: The provided secret key does not have the correct length. It should be 30 characters long.
@@ -0,0 +1,30 @@
1
+ module Booth
2
+ module Adminland
3
+ module Credentials
4
+ class Create
5
+ include ::Booth::MethodObject
6
+ include ::Booth::Logging
7
+
8
+ option :username
9
+ option :allowed_modes
10
+ option :scope, default: -> { :default }
11
+
12
+ def call
13
+ creation = ::Booth::Credentials::Create.call(
14
+ username:,
15
+ allowed_modes:,
16
+ scope:
17
+ )
18
+
19
+ return creation if creation.failure?
20
+
21
+ # Not exposing the ActiveRecord model outside of Booth.
22
+ Tron.success :credential_created,
23
+ id: creation.credential.id,
24
+ scope: creation.credential.scope,
25
+ allowed_modes: creation.credential.allowed_modes.map(&:to_sym)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end