trek 2.5.0 → 2.6.0

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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/.node-version +1 -1
  3. data/AGENTS.md +7 -4
  4. data/CHANGELOG.md +37 -0
  5. data/Gemfile +5 -0
  6. data/Gemfile.lock +16 -3
  7. data/Makefile +2 -0
  8. data/app/components/trek/dashboard/tile_component/tile_component.css +9 -0
  9. data/app/components/trek/dashboard/tile_component/tile_component.html.slim +4 -2
  10. data/app/components/trek/dashboard/tile_component.rb +12 -2
  11. data/app/components/trek/form/content_editor_component/content_editor_component.css +5 -0
  12. data/app/components/trek/form/content_editor_component/content_editor_component.html.slim +19 -93
  13. data/app/components/trek/form/content_editor_component/content_editor_component.js +94 -251
  14. data/app/components/trek/form/content_editor_component/extensions/block_view.js +40 -0
  15. data/app/components/trek/form/content_editor_component/extensions/image_block.js +28 -26
  16. data/app/components/trek/form/content_editor_component/extensions/prompts_block.js +15 -26
  17. data/app/components/trek/form/content_editor_component/extensions/table_of_contents.js +1 -3
  18. data/app/components/trek/form/content_editor_component.rb +15 -0
  19. data/app/components/trek/form/image_field_component/image_field_component.css +40 -1
  20. data/app/components/trek/form/image_field_component/image_field_component.html.slim +0 -9
  21. data/app/components/trek/form/image_field_component.rb +36 -9
  22. data/app/content/trek/content/cleaner.rb +53 -0
  23. data/app/content/trek/content/document.rb +2 -2
  24. data/app/content/trek/content/prompts_block.rb +1 -2
  25. data/app/content/trek/content.rb +6 -0
  26. data/app/controllers/concerns/trek/reorderable.rb +24 -6
  27. data/app/controllers/trek/csp_reports_controller.rb +14 -1
  28. data/app/controllers/trek/panels/prompts_controller.rb +1 -1
  29. data/app/controllers/trek/resource_controller.rb +5 -5
  30. data/app/jobs/trek/purge_expired_sessions_job.rb +4 -3
  31. data/app/models/concerns/trek/contentable.rb +15 -1
  32. data/app/models/concerns/trek/users/sign_in_code.rb +70 -0
  33. data/app/models/concerns/trek/users/sign_in_lockout.rb +4 -2
  34. data/app/views/trek/panels/links/_form.html.slim +2 -4
  35. data/app/views/trek/panels/prompts/create.turbo_stream.slim +0 -1
  36. data/config/locales/trek.en.yml +26 -11
  37. data/config/locales/trek.fr.yml +26 -11
  38. data/docs/.vitepress/config.mjs +4 -1
  39. data/docs/background-jobs.md +69 -0
  40. data/docs/content-editor.md +219 -0
  41. data/docs/customize.md +2 -0
  42. data/docs/deploy.md +2 -2
  43. data/docs/getting-started.md +1 -1
  44. data/docs/index.md +1 -1
  45. data/docs/reference/components.md +2 -2
  46. data/docs/reference/concerns/contentable.md +10 -4
  47. data/docs/reference/concerns/formattable.md +1 -1
  48. data/docs/reference/environment-variables.md +1 -0
  49. data/docs/reference/formatters.md +2 -30
  50. data/docs/reference/forms.md +1 -1
  51. data/docs/reference/generators/audit-log.md +2 -2
  52. data/docs/reference/generators/install.md +3 -2
  53. data/docs/reference/generators/public-accounts.md +141 -0
  54. data/docs/reference/generators/security-headers.md +1 -1
  55. data/docs/reference/index.md +2 -1
  56. data/docs/reference/models/user.md +3 -1
  57. data/docs/what-is-trek.md +1 -1
  58. data/lib/generators/trek/audit_log_generator.rb +54 -0
  59. data/lib/generators/trek/install/admin/users_generator.rb +6 -1
  60. data/lib/generators/trek/install/audit_log_generator.rb +9 -45
  61. data/lib/generators/trek/install/good_job_generator.rb +161 -0
  62. data/lib/generators/trek/install/model_specs_generator.rb +3 -3
  63. data/lib/generators/trek/install/ruby_generator.rb +1 -1
  64. data/lib/generators/trek/install/scalingo_generator.rb +7 -45
  65. data/lib/generators/trek/install_generator.rb +15 -0
  66. data/lib/generators/trek/public_accounts_generator.rb +267 -0
  67. data/lib/generators/trek/scalingo_generator.rb +55 -0
  68. data/lib/generators/trek/templates/.gitlab-ci.yml +1 -1
  69. data/lib/generators/trek/templates/controllers/admin/password_renewals_controller.rb +11 -0
  70. data/lib/generators/trek/templates/controllers/admin/user_sessions_controller.rb +1 -1
  71. data/lib/generators/trek/templates/controllers/admin/users_controller.rb +0 -8
  72. data/lib/generators/trek/templates/initializers/content_security_policy.rb +3 -2
  73. data/lib/generators/trek/templates/initializers/good_job.rb.tt +38 -0
  74. data/lib/generators/trek/templates/jobs/mail_delivery_job.rb +6 -0
  75. data/lib/generators/trek/templates/layouts/admin.html.slim +5 -0
  76. data/lib/generators/trek/templates/mailers/admin_mailer.rb +4 -2
  77. data/lib/generators/trek/templates/package.json.tt +1 -1
  78. data/lib/generators/trek/templates/public_accounts/api/authentication.rb.tt +76 -0
  79. data/lib/generators/trek/templates/public_accounts/api/controllers/accounts_controller.rb.tt +22 -0
  80. data/lib/generators/trek/templates/public_accounts/api/controllers/active_sessions_controller.rb.tt +21 -0
  81. data/lib/generators/trek/templates/public_accounts/api/controllers/base_controller.rb.tt +71 -0
  82. data/lib/generators/trek/templates/public_accounts/api/controllers/email_changes_controller.rb.tt +55 -0
  83. data/lib/generators/trek/templates/public_accounts/api/controllers/email_confirmations_controller.rb.tt +19 -0
  84. data/lib/generators/trek/templates/public_accounts/api/controllers/password_resets_controller.rb.tt +28 -0
  85. data/lib/generators/trek/templates/public_accounts/api/controllers/passwords_controller.rb.tt +17 -0
  86. data/lib/generators/trek/templates/public_accounts/api/controllers/registrations_controller.rb.tt +17 -0
  87. data/lib/generators/trek/templates/public_accounts/api/controllers/sessions_controller.rb.tt +45 -0
  88. data/lib/generators/trek/templates/public_accounts/api/controllers/sign_in_codes_controller.rb.tt +15 -0
  89. data/lib/generators/trek/templates/public_accounts/jobs/sign_in_code_job.rb.tt +13 -0
  90. data/lib/generators/trek/templates/public_accounts/locales/accounts.en.yml.tt +205 -0
  91. data/lib/generators/trek/templates/public_accounts/locales/accounts.fr.yml.tt +205 -0
  92. data/lib/generators/trek/templates/public_accounts/mailers/account_mailer.rb.tt +85 -0
  93. data/lib/generators/trek/templates/public_accounts/migrations/create_accounts.rb.tt +46 -0
  94. data/lib/generators/trek/templates/public_accounts/models/account/session.rb.tt +47 -0
  95. data/lib/generators/trek/templates/public_accounts/models/account/sign_in_code.rb.tt +8 -0
  96. data/lib/generators/trek/templates/public_accounts/models/account.rb.tt +73 -0
  97. data/lib/generators/trek/templates/public_accounts/spec/api/accounts_spec.rb.tt +90 -0
  98. data/lib/generators/trek/templates/public_accounts/spec/api/sign_ins_spec.rb.tt +158 -0
  99. data/lib/generators/trek/templates/public_accounts/spec/models/account/sign_in_code_spec.rb.tt +56 -0
  100. data/lib/generators/trek/templates/public_accounts/spec/models/account_spec.rb.tt +109 -0
  101. data/lib/generators/trek/templates/public_accounts/spec/web/accounts_spec.rb.tt +126 -0
  102. data/lib/generators/trek/templates/public_accounts/spec/web/authentication_spec.rb.tt +43 -0
  103. data/lib/generators/trek/templates/public_accounts/spec/web/sign_ins_spec.rb.tt +215 -0
  104. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.en.html.slim.tt +10 -0
  105. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.en.text.erb.tt +8 -0
  106. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.fr.html.slim.tt +13 -0
  107. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.fr.text.erb.tt +8 -0
  108. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.en.html.slim.tt +9 -0
  109. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.en.text.erb.tt +11 -0
  110. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.fr.html.slim.tt +11 -0
  111. data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.fr.text.erb.tt +11 -0
  112. data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.en.html.slim.tt +10 -0
  113. data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.en.text.erb.tt +8 -0
  114. data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.fr.html.slim.tt +10 -0
  115. data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.fr.text.erb.tt +8 -0
  116. data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.en.html.slim.tt +10 -0
  117. data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.en.text.erb.tt +8 -0
  118. data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.fr.html.slim.tt +13 -0
  119. data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.fr.text.erb.tt +8 -0
  120. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.en.html.slim.tt +9 -0
  121. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.en.text.erb.tt +11 -0
  122. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.fr.html.slim.tt +9 -0
  123. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.fr.text.erb.tt +11 -0
  124. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.en.html.slim.tt +23 -0
  125. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.en.text.erb.tt +14 -0
  126. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.fr.html.slim.tt +24 -0
  127. data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.fr.text.erb.tt +14 -0
  128. data/lib/generators/trek/templates/public_accounts/web/authentication.rb.tt +97 -0
  129. data/lib/generators/trek/templates/public_accounts/web/controllers/accounts_controller.rb.tt +33 -0
  130. data/lib/generators/trek/templates/public_accounts/web/controllers/active_sessions_controller.rb.tt +15 -0
  131. data/lib/generators/trek/templates/public_accounts/web/controllers/base_controller.rb.tt +52 -0
  132. data/lib/generators/trek/templates/public_accounts/web/controllers/email_changes_controller.rb.tt +85 -0
  133. data/lib/generators/trek/templates/public_accounts/web/controllers/email_confirmations_controller.rb.tt +19 -0
  134. data/lib/generators/trek/templates/public_accounts/web/controllers/password_resets_controller.rb.tt +41 -0
  135. data/lib/generators/trek/templates/public_accounts/web/controllers/passwords_controller.rb.tt +23 -0
  136. data/lib/generators/trek/templates/public_accounts/web/controllers/registrations_controller.rb.tt +24 -0
  137. data/lib/generators/trek/templates/public_accounts/web/controllers/sessions_controller.rb.tt +78 -0
  138. data/lib/generators/trek/templates/public_accounts/web/controllers/sign_in_codes_controller.rb.tt +27 -0
  139. data/lib/generators/trek/templates/public_accounts/web/views/_flash.html.slim.tt +5 -0
  140. data/lib/generators/trek/templates/public_accounts/web/views/accounts/show.html.slim.tt +52 -0
  141. data/lib/generators/trek/templates/public_accounts/web/views/email_changes/edit.html.slim.tt +17 -0
  142. data/lib/generators/trek/templates/public_accounts/web/views/email_changes/new.html.slim.tt +15 -0
  143. data/lib/generators/trek/templates/public_accounts/web/views/password_resets/edit.html.slim.tt +26 -0
  144. data/lib/generators/trek/templates/public_accounts/web/views/password_resets/new.html.slim.tt +15 -0
  145. data/lib/generators/trek/templates/public_accounts/web/views/passwords/edit.html.slim.tt +25 -0
  146. data/lib/generators/trek/templates/public_accounts/web/views/registrations/new.html.slim.tt +28 -0
  147. data/lib/generators/trek/templates/public_accounts/web/views/sessions/new.html.slim.tt +38 -0
  148. data/lib/generators/trek/templates/public_accounts/web/views/sign_in_codes/new.html.slim.tt +17 -0
  149. data/lib/generators/trek/templates/spec/jobs/mail_delivery_job_spec.rb +11 -0
  150. data/lib/generators/trek/templates/spec/mailers/admin_mailer_spec.rb +10 -0
  151. data/lib/generators/trek/templates/spec/requests/admin/fragments_spec.rb +1 -1
  152. data/lib/generators/trek/templates/spec/requests/admin/good_job_spec.rb +34 -0
  153. data/lib/generators/trek/templates/spec/requests/admin/pages_spec.rb +1 -1
  154. data/lib/generators/trek/templates/spec/requests/admin/panels_spec.rb +16 -0
  155. data/lib/generators/trek/templates/spec/requests/admin/password_renewal_spec.rb +2 -2
  156. data/lib/generators/trek/templates/spec/requests/admin/sign_in_lockout_spec.rb +1 -1
  157. data/lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb +2 -2
  158. data/lib/generators/trek/templates/views/admin/users/index.html.slim +1 -1
  159. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.en.html.slim +1 -1
  160. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.en.text.erb +1 -1
  161. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.fr.html.slim +1 -1
  162. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.fr.text.erb +1 -1
  163. data/lib/trek/csp_reports.rb +22 -4
  164. data/lib/trek/typography_formatter.rb +35 -4
  165. data/lib/trek/version.rb +1 -1
  166. data/package.json +12 -8
  167. data/trek.gemspec +1 -1
  168. data/vitest.config.mjs +24 -0
  169. data/yarn.lock +1004 -288
  170. metadata +90 -3
@@ -0,0 +1,55 @@
1
+ <% if code? -%>
2
+ # Changing the account's email: POST { email } sends a code to the new address, PATCH { email, otp }
3
+ # confirms it's theirs and changes it.
4
+ <% else -%>
5
+ # Changing the account's email: POST { email } sends a link to the new address, PATCH { token } (the
6
+ # link's, which the app opens, possibly signed out) confirms it's theirs and changes it.
7
+ <% end -%>
8
+ class <%= controllers_module %>::EmailChangesController < <%= controllers_module %>::BaseController
9
+ <% if password? -%>
10
+ allow_unauthenticated_access only: :update
11
+ <% end -%>
12
+ rate_limit to: 10, within: 15.minutes, with: -> { render_rate_limited }
13
+
14
+ def create
15
+ email = normalized_email(params[:email])
16
+ return render_api_error :unprocessable_content, :invalid_email unless valid_email?(email) && email != Current.<%= file_name %>.email
17
+ return render_rate_limited if too_many_emails_to?(email)
18
+
19
+ <% if code? -%>
20
+ <%= accounts_module %>::SignInCodeJob.perform_later(email, purpose: "email_change", <%= file_name %>: Current.<%= file_name %>)
21
+ <% else -%>
22
+ Current.<%= file_name %>.update_column(:unconfirmed_email, email) # rubocop:disable Rails/SkipsModelValidations -- checked above
23
+ <%= class_name %>Mailer.email_change(Current.<%= file_name %>).deliver_later
24
+ <% end -%>
25
+ head :accepted
26
+ end
27
+
28
+ # Taken by another account is only told once the <%= code? ? "code" : "link" %> proved the address is theirs.
29
+ def update
30
+ <% if code? -%>
31
+ return render_rate_limited if too_many_failed_sign_ins_from_ip?
32
+
33
+ email = normalized_email(params[:email])
34
+ unless <%= class_name %>::SignInCode.redeem(email, params[:otp], purpose: "email_change", <%= file_name %>: Current.<%= file_name %>)
35
+ count_failed_sign_in_from_ip
36
+ return render_api_error :unprocessable_content, :invalid_code
37
+ end
38
+
39
+ if Current.<%= file_name %>.update(email:)
40
+ render json: <%= file_name %>_json(Current.<%= file_name %>)
41
+ else
42
+ render_api_error :conflict, :email_taken
43
+ end
44
+ <% else -%>
45
+ @<%= file_name %> = <%= class_name %>.find_by_token_for(:email_change, params[:token].to_s)
46
+ return render_api_error :unprocessable_content, :invalid_token unless @<%= file_name %>
47
+
48
+ if @<%= file_name %>.change_email!
49
+ render json: <%= file_name %>_json(@<%= file_name %>)
50
+ else
51
+ render_api_error :conflict, :email_taken
52
+ end
53
+ <% end -%>
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ # POST: sends the link confirming the account's email again. PATCH { token }: the token of the link,
2
+ # which the app opens, possibly signed out: the token is enough.
3
+ class <%= controllers_module %>::EmailConfirmationsController < <%= controllers_module %>::BaseController
4
+ allow_unauthenticated_access only: :update
5
+
6
+ def create
7
+ <%= file_name %> = Current.<%= file_name %>
8
+ <%= class_name %>Mailer.email_confirmation(<%= file_name %>).deliver_later unless <%= file_name %>.confirmed? || too_many_emails_to?(<%= file_name %>.email)
9
+ head :accepted
10
+ end
11
+
12
+ def update
13
+ @<%= file_name %> = <%= class_name %>.find_by_token_for(:email_confirmation, params[:token].to_s)
14
+ return render_api_error :unprocessable_content, :invalid_token unless @<%= file_name %>
15
+
16
+ @<%= file_name %>.confirm_email!
17
+ render json: <%= file_name %>_json(@<%= file_name %>)
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ # POST { email }: emails a link to choose a new password, if the email has an account, with the same
2
+ # answer (202) either way. PATCH { token, password }: the token of the link, which the app opens, and
3
+ # the new password; signs out every session.
4
+ class <%= controllers_module %>::PasswordResetsController < <%= controllers_module %>::BaseController
5
+ allow_unauthenticated_access
6
+ rate_limit to: 10, within: 15.minutes, with: -> { render_rate_limited }
7
+
8
+ def create
9
+ email = normalized_email(params[:email])
10
+ @<%= file_name %> = <%= class_name %>.find_by(email:) if valid_email?(email)
11
+ <%= class_name %>Mailer.password_reset(@<%= file_name %>).deliver_later if @<%= file_name %> && !too_many_emails_to?(email)
12
+
13
+ head :accepted
14
+ end
15
+
16
+ def update
17
+ @<%= file_name %> = <%= class_name %>.find_by_token_for(:password_reset, params[:token].to_s)
18
+ return render_api_error :unprocessable_content, :invalid_token unless @<%= file_name %>
19
+
20
+ @<%= file_name %>.password = params[:password].to_s
21
+ if @<%= file_name %>.save
22
+ @<%= file_name %>.sessions.delete_all
23
+ head :no_content
24
+ else
25
+ render_api_error :unprocessable_content, :invalid, errors: @<%= file_name %>.errors
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # PATCH { password }: changes the account's password, after a recent sign-in; a forgotten one goes
2
+ # through the password reset. Signs out the account's other sessions.
3
+ class <%= controllers_module %>::PasswordsController < <%= controllers_module %>::BaseController
4
+ before_action :require_recent_<%= file_name %>_sign_in
5
+
6
+ def update
7
+ <%= file_name %> = Current.<%= file_name %>
8
+ <%= file_name %>.password = params[:password].to_s
9
+
10
+ if <%= file_name %>.save
11
+ <%= file_name %>.sessions.where.not(id: Current.<%= file_name %>_session.id).delete_all
12
+ head :no_content
13
+ else
14
+ render_api_error :unprocessable_content, :invalid, errors: <%= file_name %>.errors
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # POST { email, password, name }: signs up and in, answering with the token; the email is confirmed
2
+ # later by a link (confirmed_at), which the app opens and sends to PATCH /account/confirmation.
3
+ class <%= controllers_module %>::RegistrationsController < <%= controllers_module %>::BaseController
4
+ allow_unauthenticated_access
5
+ rate_limit to: 5, within: 15.minutes, with: -> { render_rate_limited }
6
+
7
+ def create
8
+ @<%= file_name %> = <%= class_name %>.new(params.permit(:email, :name, :password))
9
+
10
+ if @<%= file_name %>.save
11
+ <%= class_name %>Mailer.email_confirmation(@<%= file_name %>).deliver_later
12
+ render_new_session @<%= file_name %>
13
+ else
14
+ render_api_error :unprocessable_content, :invalid, errors: @<%= file_name %>.errors
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ <% if code? -%>
2
+ # POST { email, otp }: signs in with the emailed code, to the email's account, created if it had none;
3
+ # answers with the token for the next requests. DELETE: signs out, ending the token's session.
4
+ <% else -%>
5
+ # POST { email, password }: signs in, and answers with the token for the next requests. DELETE: signs
6
+ # out, ending the token's session.
7
+ <% end -%>
8
+ class <%= controllers_module %>::SessionsController < <%= controllers_module %>::BaseController
9
+ allow_unauthenticated_access only: :create
10
+ rate_limit to: <%= code? ? 10 : 5 %>, within: 3.minutes, only: :create, with: -> { render_rate_limited }
11
+
12
+ def create
13
+ return render_rate_limited if too_many_failed_sign_ins_from_ip?
14
+
15
+ <% if code? -%>
16
+ email = normalized_email(params[:email])
17
+ unless <%= class_name %>::SignInCode.redeem(email, params[:otp])
18
+ count_failed_sign_in_from_ip
19
+ return render_api_error :unauthorized, :invalid_code
20
+ end
21
+
22
+ @<%= file_name %> = <%= class_name %>.for_verified_email!(email)
23
+ render_new_session @<%= file_name %>
24
+ <% else -%>
25
+ # Hashes the password sent even when no account matches, so the response takes as long
26
+ <%= file_name %> = <%= class_name %>.authenticate_by(email: params[:email].to_s, password: params[:password].to_s)
27
+ @<%= file_name %> = <%= file_name %> || <%= class_name %>.find_by(email: params[:email].to_s)
28
+
29
+ # A locked account is refused even with the right password, with the same answer as a wrong one
30
+ if <%= file_name %> && !<%= file_name %>.sign_in_locked?
31
+ <%= file_name %>.record_successful_sign_in!
32
+ render_new_session <%= file_name %>
33
+ else
34
+ @<%= file_name %>&.record_failed_sign_in!
35
+ count_failed_sign_in_from_ip
36
+ render_api_error :unauthorized, :invalid_password
37
+ end
38
+ <% end -%>
39
+ end
40
+
41
+ def destroy
42
+ terminate_<%= file_name %>_session
43
+ head :no_content
44
+ end
45
+ end
@@ -0,0 +1,15 @@
1
+ # POST { email }: emails a code to sign in with. Whether or not the email has an account, the answer
2
+ # is the same (202); the code creates the account on its first use.
3
+ class <%= controllers_module %>::SignInCodesController < <%= controllers_module %>::BaseController
4
+ allow_unauthenticated_access
5
+ rate_limit to: 10, within: 15.minutes, with: -> { render_rate_limited }
6
+
7
+ def create
8
+ email = normalized_email(params[:email])
9
+ return render_api_error :unprocessable_content, :invalid_email unless valid_email?(email)
10
+ return render_rate_limited if too_many_emails_to?(email)
11
+
12
+ <%= accounts_module %>::SignInCodeJob.perform_later(email)
13
+ head :accepted
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # Issues a code and emails it, in the job rather than the request: the code in clear only exists
2
+ # here, never in the job's arguments, which the queue keeps. Active Job runs it in the request's
3
+ # locale.
4
+ class <%= accounts_module %>::SignInCodeJob < ApplicationJob
5
+ # To sign in to `email`'s account (created on the code's first use), or for `<%= file_name %>` to
6
+ # change its email to `email` (purpose: "email_change").
7
+ def perform(email, purpose: "sign_in", <%= file_name %>: nil)
8
+ sign_in_code = <%= class_name %>::SignInCode.issue(email, purpose:, <%= file_name %>:)
9
+ mail = purpose == "email_change" ? :email_change_code : :sign_in_code
10
+
11
+ <%= class_name %>Mailer.public_send(mail, email, sign_in_code.code).deliver_now
12
+ end
13
+ end
@@ -0,0 +1,205 @@
1
+ en:
2
+ activerecord:
3
+ attributes:
4
+ <%= file_name %>:
5
+ email: Email
6
+ name: Name
7
+ <% if password? -%>
8
+ password: Password
9
+ unconfirmed_email: New email
10
+ errors:
11
+ models:
12
+ <%= file_name %>:
13
+ attributes:
14
+ password:
15
+ common: is among the most common passwords, choose a less predictable one
16
+ digit_and_symbol: must contain a digit and a symbol, unless it is at least %{count} characters long
17
+ personal: must not contain your name or email
18
+ pwned: appears in known data breaches, choose another one
19
+ sequence: must not contain a sequence such as 12345, azerty or aaaaa
20
+ <% end -%>
21
+ models:
22
+ <%= file_name %>:
23
+ one: <%= human_name %>
24
+ other: <%= human_name.pluralize %>
25
+ <%= file_name %>_mailer:
26
+ <% if code? -%>
27
+ email_change_code:
28
+ subject: Confirm your new email address
29
+ sign_in_code:
30
+ subject: Your sign-in code
31
+ <% else -%>
32
+ email_change:
33
+ subject: Confirm your new email address
34
+ email_confirmation:
35
+ subject: Confirm your email address
36
+ password_reset:
37
+ subject: Choose a new password
38
+ sign_in_locked:
39
+ subject: Signing in to your account is paused
40
+ <% end -%>
41
+ <% unless model_only? -%>
42
+ <%= accounts_path %>:
43
+ <% if api? -%>
44
+ api:
45
+ errors:
46
+ email_taken: This email is already used by another account.
47
+ invalid: Some fields are invalid.
48
+ <% if code? -%>
49
+ invalid_code: This code is wrong or expired. Check the last email, or ask for a new code.
50
+ <% end -%>
51
+ invalid_email: Enter a valid email address.
52
+ <% if password? -%>
53
+ invalid_password: Wrong email or password.
54
+ invalid_token: This link is wrong or expired. Ask for a new one.
55
+ <% end -%>
56
+ rate_limited: Too many attempts. Try again in a few minutes.
57
+ recent_sign_in_needed: For your security, sign in again to continue.
58
+ sign_in_needed: Sign in to continue.
59
+ <% else -%>
60
+ accounts:
61
+ destroy:
62
+ success: Your account is deleted.
63
+ show:
64
+ change_email: Change
65
+ <% if password? -%>
66
+ change_password: Change your password
67
+ <% end -%>
68
+ delete: Delete my account
69
+ delete_confirm: Delete your account for good?
70
+ delete_intro: Your account and everything attached to it are deleted for good.
71
+ delete_title: Delete your account
72
+ <% if password? -%>
73
+ pending_email: "%{email} will replace it once you follow the link we sent there."
74
+ resend_confirmation: Send the confirmation link again
75
+ <% end -%>
76
+ save: Save
77
+ sessions: Signed-in browsers
78
+ sign_out: Sign out
79
+ sign_out_others: Sign out of all other browsers
80
+ sign_out_session: Sign out
81
+ signed_in: signed in on %{date}
82
+ this_browser: "(this browser)"
83
+ title: Your account
84
+ <% if password? -%>
85
+ unconfirmed: This email isn't confirmed yet. Follow the link we sent to it.
86
+ <% end -%>
87
+ update:
88
+ success: Your account is saved.
89
+ active_sessions:
90
+ destroy:
91
+ success: The browser is signed out.
92
+ destroy_others:
93
+ success: The other browsers are signed out.
94
+ authentication:
95
+ rate_limited: Too many attempts. Try again in a few minutes.
96
+ recent_sign_in_needed: For your security, sign in again to continue.
97
+ sign_in_needed: Sign in to continue.
98
+ email_changes:
99
+ create:
100
+ invalid_email: Enter a valid email address, other than your current one.
101
+ <% if code? -%>
102
+ sent: We sent a code to %{email}.
103
+ edit:
104
+ back: Back to your account
105
+ code: Code
106
+ intro: Enter the code we sent to %{email}.
107
+ submit: Confirm
108
+ title: Confirm your new email
109
+ <% else -%>
110
+ sent: We sent a link to %{email}. Your email changes once you follow it.
111
+ <% end -%>
112
+ new:
113
+ back: Back to your account
114
+ email: New email
115
+ <% if code? -%>
116
+ intro: We'll send a code to the new address to confirm it's yours.
117
+ submit: Send me a code
118
+ <% else -%>
119
+ intro: We'll send a link to the new address to confirm it's yours.
120
+ submit: Send me a link
121
+ <% end -%>
122
+ title: Change your email
123
+ <% if code? -%>
124
+ update:
125
+ failure: This code is wrong or expired.
126
+ success: Your email is changed.
127
+ taken: This email is already used by another account.
128
+ <% else -%>
129
+ show:
130
+ invalid: This link is wrong or expired. Ask for a new one from your account.
131
+ success: Your email is changed.
132
+ taken: This email is already used by another account.
133
+ email_confirmations:
134
+ create:
135
+ sent: We sent a confirmation link to %{email}.
136
+ show:
137
+ invalid: This link is wrong or expired. Ask for a new one from your account.
138
+ success: Your email is confirmed.
139
+ password_resets:
140
+ create:
141
+ sent: If this email has an account, we sent it a link to choose a new password.
142
+ edit:
143
+ rules: At least %{min} characters, with a digit and a symbol unless it is at least %{passphrase} characters long.
144
+ submit: Save
145
+ title: Choose a new password
146
+ invalid: This link is wrong or expired. Ask for a new one.
147
+ new:
148
+ intro: Enter your email. We'll send you a link to choose a new password.
149
+ sign_in: Back to signing in
150
+ submit: Send me a link
151
+ title: Forgot your password?
152
+ update:
153
+ success: Your password is saved. Sign in with it.
154
+ passwords:
155
+ edit:
156
+ back: Back to your account
157
+ intro: At least %{min} characters, with a digit and a symbol unless it is at least %{passphrase} characters long.
158
+ submit: Save
159
+ title: Your password
160
+ update:
161
+ success: Your password is saved. Your other browsers are signed out.
162
+ registrations:
163
+ create:
164
+ success: Welcome! We sent a link to %{email} to confirm it.
165
+ new:
166
+ rules: At least %{min} characters, with a digit and a symbol unless it is at least %{passphrase} characters long.
167
+ sign_in: I already have an account
168
+ submit: Sign up
169
+ title: Create your account
170
+ <% end -%>
171
+ sessions:
172
+ create:
173
+ <% if code? -%>
174
+ failure: This code is wrong or expired. Check the last email, or ask for a new code.
175
+ <% else -%>
176
+ failure: Wrong email or password.
177
+ <% end -%>
178
+ success: You're signed in.
179
+ destroy:
180
+ success: You're signed out.
181
+ new:
182
+ <% if code? -%>
183
+ code: Code
184
+ intro: We sent a 6-digit code to %{email}. It is valid for %{validity}.
185
+ other_email: Use another email
186
+ submit: Sign in
187
+ title: Enter your code
188
+ <% else -%>
189
+ forgot_password: Forgot your password?
190
+ sign_up: Create an account
191
+ submit: Sign in
192
+ title: Sign in
193
+ <% end -%>
194
+ <% if code? -%>
195
+ sign_in_codes:
196
+ create:
197
+ invalid_email: Enter a valid email address.
198
+ sent: We sent a code to %{email}.
199
+ new:
200
+ intro: Enter your email. We'll send you a code to sign in, or to create your account if you don't have one yet.
201
+ submit: Send me a code
202
+ title: Sign in
203
+ <% end -%>
204
+ <% end -%>
205
+ <% end -%>
@@ -0,0 +1,205 @@
1
+ fr:
2
+ activerecord:
3
+ attributes:
4
+ <%= file_name %>:
5
+ email: Adresse e-mail
6
+ name: Nom
7
+ <% if password? -%>
8
+ password: Mot de passe
9
+ unconfirmed_email: Nouvelle adresse e-mail
10
+ errors:
11
+ models:
12
+ <%= file_name %>:
13
+ attributes:
14
+ password:
15
+ common: fait partie des mots de passe les plus courants, choisissez-en un moins prévisible
16
+ digit_and_symbol: doit contenir un chiffre et un caractère spécial, sauf s'il fait au moins %{count} caractères
17
+ personal: ne doit pas contenir votre nom ni votre adresse e-mail
18
+ pwned: figure dans des fuites de données connues, choisissez-en un autre
19
+ sequence: ne doit pas contenir de suite comme 12345, azerty ou aaaaa
20
+ <% end -%>
21
+ models:
22
+ <%= file_name %>:
23
+ one: <%= human_name %>
24
+ other: <%= human_name.pluralize %>
25
+ <%= file_name %>_mailer:
26
+ <% if code? -%>
27
+ email_change_code:
28
+ subject: Confirmez votre nouvelle adresse e-mail
29
+ sign_in_code:
30
+ subject: Votre code de connexion
31
+ <% else -%>
32
+ email_change:
33
+ subject: Confirmez votre nouvelle adresse e-mail
34
+ email_confirmation:
35
+ subject: Confirmez votre adresse e-mail
36
+ password_reset:
37
+ subject: Choisissez un nouveau mot de passe
38
+ sign_in_locked:
39
+ subject: La connexion à votre compte est suspendue
40
+ <% end -%>
41
+ <% unless model_only? -%>
42
+ <%= accounts_path %>:
43
+ <% if api? -%>
44
+ api:
45
+ errors:
46
+ email_taken: Cette adresse e-mail est déjà utilisée par un autre compte.
47
+ invalid: Certains champs ne sont pas valides.
48
+ <% if code? -%>
49
+ invalid_code: Ce code est erroné ou expiré. Vérifiez le dernier e-mail, ou demandez un nouveau code.
50
+ <% end -%>
51
+ invalid_email: Saisissez une adresse e-mail valide.
52
+ <% if password? -%>
53
+ invalid_password: Adresse e-mail ou mot de passe incorrect.
54
+ invalid_token: Ce lien est erroné ou expiré. Demandez-en un nouveau.
55
+ <% end -%>
56
+ rate_limited: Trop de tentatives. Réessayez dans quelques minutes.
57
+ recent_sign_in_needed: Pour votre sécurité, reconnectez-vous pour continuer.
58
+ sign_in_needed: Connectez-vous pour continuer.
59
+ <% else -%>
60
+ accounts:
61
+ destroy:
62
+ success: Votre compte est supprimé.
63
+ show:
64
+ change_email: Changer
65
+ <% if password? -%>
66
+ change_password: Changer votre mot de passe
67
+ <% end -%>
68
+ delete: Supprimer mon compte
69
+ delete_confirm: Supprimer votre compte définitivement ?
70
+ delete_intro: Votre compte et tout ce qui y est rattaché sont supprimés définitivement.
71
+ delete_title: Supprimer votre compte
72
+ <% if password? -%>
73
+ pending_email: "%{email} la remplacera une fois le lien envoyé à cette adresse suivi."
74
+ resend_confirmation: Renvoyer le lien de confirmation
75
+ <% end -%>
76
+ save: Enregistrer
77
+ sessions: Navigateurs connectés
78
+ sign_out: Se déconnecter
79
+ sign_out_others: Déconnecter tous les autres navigateurs
80
+ sign_out_session: Déconnecter
81
+ signed_in: connecté le %{date}
82
+ this_browser: "(ce navigateur)"
83
+ title: Votre compte
84
+ <% if password? -%>
85
+ unconfirmed: Cette adresse n'est pas encore confirmée. Suivez le lien que nous y avons envoyé.
86
+ <% end -%>
87
+ update:
88
+ success: Votre compte est enregistré.
89
+ active_sessions:
90
+ destroy:
91
+ success: Le navigateur est déconnecté.
92
+ destroy_others:
93
+ success: Les autres navigateurs sont déconnectés.
94
+ authentication:
95
+ rate_limited: Trop de tentatives. Réessayez dans quelques minutes.
96
+ recent_sign_in_needed: Pour votre sécurité, reconnectez-vous pour continuer.
97
+ sign_in_needed: Connectez-vous pour continuer.
98
+ email_changes:
99
+ create:
100
+ invalid_email: Saisissez une adresse e-mail valide, autre que l'actuelle.
101
+ <% if code? -%>
102
+ sent: Nous avons envoyé un code à %{email}.
103
+ edit:
104
+ back: Retour à votre compte
105
+ code: Code
106
+ intro: Saisissez le code envoyé à %{email}.
107
+ submit: Confirmer
108
+ title: Confirmez votre nouvelle adresse
109
+ <% else -%>
110
+ sent: Nous avons envoyé un lien à %{email}. Votre adresse changera une fois ce lien suivi.
111
+ <% end -%>
112
+ new:
113
+ back: Retour à votre compte
114
+ email: Nouvelle adresse e-mail
115
+ <% if code? -%>
116
+ intro: Nous enverrons un code à la nouvelle adresse pour confirmer qu'elle est bien à vous.
117
+ submit: Recevoir un code
118
+ <% else -%>
119
+ intro: Nous enverrons un lien à la nouvelle adresse pour confirmer qu'elle est bien à vous.
120
+ submit: Recevoir un lien
121
+ <% end -%>
122
+ title: Changer votre adresse e-mail
123
+ <% if code? -%>
124
+ update:
125
+ failure: Ce code est erroné ou expiré.
126
+ success: Votre adresse e-mail est changée.
127
+ taken: Cette adresse e-mail est déjà utilisée par un autre compte.
128
+ <% else -%>
129
+ show:
130
+ invalid: Ce lien est erroné ou expiré. Demandez-en un nouveau depuis votre compte.
131
+ success: Votre adresse e-mail est changée.
132
+ taken: Cette adresse e-mail est déjà utilisée par un autre compte.
133
+ email_confirmations:
134
+ create:
135
+ sent: Nous avons envoyé un lien de confirmation à %{email}.
136
+ show:
137
+ invalid: Ce lien est erroné ou expiré. Demandez-en un nouveau depuis votre compte.
138
+ success: Votre adresse e-mail est confirmée.
139
+ password_resets:
140
+ create:
141
+ sent: Si cette adresse a un compte, nous lui avons envoyé un lien pour choisir un nouveau mot de passe.
142
+ edit:
143
+ rules: Au moins %{min} caractères, avec un chiffre et un caractère spécial s'il fait moins de %{passphrase} caractères.
144
+ submit: Enregistrer
145
+ title: Choisissez un nouveau mot de passe
146
+ invalid: Ce lien est erroné ou expiré. Demandez-en un nouveau.
147
+ new:
148
+ intro: Saisissez votre adresse e-mail. Nous vous enverrons un lien pour choisir un nouveau mot de passe.
149
+ sign_in: Retour à la connexion
150
+ submit: Recevoir un lien
151
+ title: Mot de passe oublié ?
152
+ update:
153
+ success: Votre mot de passe est enregistré. Connectez-vous avec.
154
+ passwords:
155
+ edit:
156
+ back: Retour à votre compte
157
+ intro: Au moins %{min} caractères, avec un chiffre et un caractère spécial s'il fait moins de %{passphrase} caractères.
158
+ submit: Enregistrer
159
+ title: Votre mot de passe
160
+ update:
161
+ success: Votre mot de passe est enregistré. Vos autres navigateurs sont déconnectés.
162
+ registrations:
163
+ create:
164
+ success: Bienvenue ! Nous avons envoyé un lien à %{email} pour la confirmer.
165
+ new:
166
+ rules: Au moins %{min} caractères, avec un chiffre et un caractère spécial s'il fait moins de %{passphrase} caractères.
167
+ sign_in: J'ai déjà un compte
168
+ submit: Créer mon compte
169
+ title: Créer votre compte
170
+ <% end -%>
171
+ sessions:
172
+ create:
173
+ <% if code? -%>
174
+ failure: Ce code est erroné ou expiré. Vérifiez le dernier e-mail, ou demandez un nouveau code.
175
+ <% else -%>
176
+ failure: Adresse e-mail ou mot de passe incorrect.
177
+ <% end -%>
178
+ success: Vous êtes connecté.
179
+ destroy:
180
+ success: Vous êtes déconnecté.
181
+ new:
182
+ <% if code? -%>
183
+ code: Code
184
+ intro: Nous avons envoyé un code à 6 chiffres à %{email}. Il est valable %{validity}.
185
+ other_email: Utiliser une autre adresse
186
+ submit: Se connecter
187
+ title: Saisissez votre code
188
+ <% else -%>
189
+ forgot_password: Mot de passe oublié ?
190
+ sign_up: Créer un compte
191
+ submit: Se connecter
192
+ title: Se connecter
193
+ <% end -%>
194
+ <% if code? -%>
195
+ sign_in_codes:
196
+ create:
197
+ invalid_email: Saisissez une adresse e-mail valide.
198
+ sent: Nous avons envoyé un code à %{email}.
199
+ new:
200
+ intro: Saisissez votre adresse e-mail. Nous vous enverrons un code pour vous connecter, ou pour créer votre compte si vous n'en avez pas encore.
201
+ submit: Recevoir un code
202
+ title: Se connecter
203
+ <% end -%>
204
+ <% end -%>
205
+ <% end -%>