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.
- checksums.yaml +4 -4
- data/.node-version +1 -1
- data/AGENTS.md +7 -4
- data/CHANGELOG.md +37 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +16 -3
- data/Makefile +2 -0
- data/app/components/trek/dashboard/tile_component/tile_component.css +9 -0
- data/app/components/trek/dashboard/tile_component/tile_component.html.slim +4 -2
- data/app/components/trek/dashboard/tile_component.rb +12 -2
- data/app/components/trek/form/content_editor_component/content_editor_component.css +5 -0
- data/app/components/trek/form/content_editor_component/content_editor_component.html.slim +19 -93
- data/app/components/trek/form/content_editor_component/content_editor_component.js +94 -251
- data/app/components/trek/form/content_editor_component/extensions/block_view.js +40 -0
- data/app/components/trek/form/content_editor_component/extensions/image_block.js +28 -26
- data/app/components/trek/form/content_editor_component/extensions/prompts_block.js +15 -26
- data/app/components/trek/form/content_editor_component/extensions/table_of_contents.js +1 -3
- data/app/components/trek/form/content_editor_component.rb +15 -0
- data/app/components/trek/form/image_field_component/image_field_component.css +40 -1
- data/app/components/trek/form/image_field_component/image_field_component.html.slim +0 -9
- data/app/components/trek/form/image_field_component.rb +36 -9
- data/app/content/trek/content/cleaner.rb +53 -0
- data/app/content/trek/content/document.rb +2 -2
- data/app/content/trek/content/prompts_block.rb +1 -2
- data/app/content/trek/content.rb +6 -0
- data/app/controllers/concerns/trek/reorderable.rb +24 -6
- data/app/controllers/trek/csp_reports_controller.rb +14 -1
- data/app/controllers/trek/panels/prompts_controller.rb +1 -1
- data/app/controllers/trek/resource_controller.rb +5 -5
- data/app/jobs/trek/purge_expired_sessions_job.rb +4 -3
- data/app/models/concerns/trek/contentable.rb +15 -1
- data/app/models/concerns/trek/users/sign_in_code.rb +70 -0
- data/app/models/concerns/trek/users/sign_in_lockout.rb +4 -2
- data/app/views/trek/panels/links/_form.html.slim +2 -4
- data/app/views/trek/panels/prompts/create.turbo_stream.slim +0 -1
- data/config/locales/trek.en.yml +26 -11
- data/config/locales/trek.fr.yml +26 -11
- data/docs/.vitepress/config.mjs +4 -1
- data/docs/background-jobs.md +69 -0
- data/docs/content-editor.md +219 -0
- data/docs/customize.md +2 -0
- data/docs/deploy.md +2 -2
- data/docs/getting-started.md +1 -1
- data/docs/index.md +1 -1
- data/docs/reference/components.md +2 -2
- data/docs/reference/concerns/contentable.md +10 -4
- data/docs/reference/concerns/formattable.md +1 -1
- data/docs/reference/environment-variables.md +1 -0
- data/docs/reference/formatters.md +2 -30
- data/docs/reference/forms.md +1 -1
- data/docs/reference/generators/audit-log.md +2 -2
- data/docs/reference/generators/install.md +3 -2
- data/docs/reference/generators/public-accounts.md +141 -0
- data/docs/reference/generators/security-headers.md +1 -1
- data/docs/reference/index.md +2 -1
- data/docs/reference/models/user.md +3 -1
- data/docs/what-is-trek.md +1 -1
- data/lib/generators/trek/audit_log_generator.rb +54 -0
- data/lib/generators/trek/install/admin/users_generator.rb +6 -1
- data/lib/generators/trek/install/audit_log_generator.rb +9 -45
- data/lib/generators/trek/install/good_job_generator.rb +161 -0
- data/lib/generators/trek/install/model_specs_generator.rb +3 -3
- data/lib/generators/trek/install/ruby_generator.rb +1 -1
- data/lib/generators/trek/install/scalingo_generator.rb +7 -45
- data/lib/generators/trek/install_generator.rb +15 -0
- data/lib/generators/trek/public_accounts_generator.rb +267 -0
- data/lib/generators/trek/scalingo_generator.rb +55 -0
- data/lib/generators/trek/templates/.gitlab-ci.yml +1 -1
- data/lib/generators/trek/templates/controllers/admin/password_renewals_controller.rb +11 -0
- data/lib/generators/trek/templates/controllers/admin/user_sessions_controller.rb +1 -1
- data/lib/generators/trek/templates/controllers/admin/users_controller.rb +0 -8
- data/lib/generators/trek/templates/initializers/content_security_policy.rb +3 -2
- data/lib/generators/trek/templates/initializers/good_job.rb.tt +38 -0
- data/lib/generators/trek/templates/jobs/mail_delivery_job.rb +6 -0
- data/lib/generators/trek/templates/layouts/admin.html.slim +5 -0
- data/lib/generators/trek/templates/mailers/admin_mailer.rb +4 -2
- data/lib/generators/trek/templates/package.json.tt +1 -1
- data/lib/generators/trek/templates/public_accounts/api/authentication.rb.tt +76 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/accounts_controller.rb.tt +22 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/active_sessions_controller.rb.tt +21 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/base_controller.rb.tt +71 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/email_changes_controller.rb.tt +55 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/email_confirmations_controller.rb.tt +19 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/password_resets_controller.rb.tt +28 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/passwords_controller.rb.tt +17 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/registrations_controller.rb.tt +17 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/sessions_controller.rb.tt +45 -0
- data/lib/generators/trek/templates/public_accounts/api/controllers/sign_in_codes_controller.rb.tt +15 -0
- data/lib/generators/trek/templates/public_accounts/jobs/sign_in_code_job.rb.tt +13 -0
- data/lib/generators/trek/templates/public_accounts/locales/accounts.en.yml.tt +205 -0
- data/lib/generators/trek/templates/public_accounts/locales/accounts.fr.yml.tt +205 -0
- data/lib/generators/trek/templates/public_accounts/mailers/account_mailer.rb.tt +85 -0
- data/lib/generators/trek/templates/public_accounts/migrations/create_accounts.rb.tt +46 -0
- data/lib/generators/trek/templates/public_accounts/models/account/session.rb.tt +47 -0
- data/lib/generators/trek/templates/public_accounts/models/account/sign_in_code.rb.tt +8 -0
- data/lib/generators/trek/templates/public_accounts/models/account.rb.tt +73 -0
- data/lib/generators/trek/templates/public_accounts/spec/api/accounts_spec.rb.tt +90 -0
- data/lib/generators/trek/templates/public_accounts/spec/api/sign_ins_spec.rb.tt +158 -0
- data/lib/generators/trek/templates/public_accounts/spec/models/account/sign_in_code_spec.rb.tt +56 -0
- data/lib/generators/trek/templates/public_accounts/spec/models/account_spec.rb.tt +109 -0
- data/lib/generators/trek/templates/public_accounts/spec/web/accounts_spec.rb.tt +126 -0
- data/lib/generators/trek/templates/public_accounts/spec/web/authentication_spec.rb.tt +43 -0
- data/lib/generators/trek/templates/public_accounts/spec/web/sign_ins_spec.rb.tt +215 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.en.html.slim.tt +10 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.en.text.erb.tt +8 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.fr.html.slim.tt +13 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change.fr.text.erb.tt +8 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.en.html.slim.tt +9 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.en.text.erb.tt +11 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.fr.html.slim.tt +11 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_change_code.fr.text.erb.tt +11 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.en.html.slim.tt +10 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.en.text.erb.tt +8 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.fr.html.slim.tt +10 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/email_confirmation.fr.text.erb.tt +8 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.en.html.slim.tt +10 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.en.text.erb.tt +8 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.fr.html.slim.tt +13 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/password_reset.fr.text.erb.tt +8 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.en.html.slim.tt +9 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.en.text.erb.tt +11 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.fr.html.slim.tt +9 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_code.fr.text.erb.tt +11 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.en.html.slim.tt +23 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.en.text.erb.tt +14 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.fr.html.slim.tt +24 -0
- data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.fr.text.erb.tt +14 -0
- data/lib/generators/trek/templates/public_accounts/web/authentication.rb.tt +97 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/accounts_controller.rb.tt +33 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/active_sessions_controller.rb.tt +15 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/base_controller.rb.tt +52 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/email_changes_controller.rb.tt +85 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/email_confirmations_controller.rb.tt +19 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/password_resets_controller.rb.tt +41 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/passwords_controller.rb.tt +23 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/registrations_controller.rb.tt +24 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/sessions_controller.rb.tt +78 -0
- data/lib/generators/trek/templates/public_accounts/web/controllers/sign_in_codes_controller.rb.tt +27 -0
- data/lib/generators/trek/templates/public_accounts/web/views/_flash.html.slim.tt +5 -0
- data/lib/generators/trek/templates/public_accounts/web/views/accounts/show.html.slim.tt +52 -0
- data/lib/generators/trek/templates/public_accounts/web/views/email_changes/edit.html.slim.tt +17 -0
- data/lib/generators/trek/templates/public_accounts/web/views/email_changes/new.html.slim.tt +15 -0
- data/lib/generators/trek/templates/public_accounts/web/views/password_resets/edit.html.slim.tt +26 -0
- data/lib/generators/trek/templates/public_accounts/web/views/password_resets/new.html.slim.tt +15 -0
- data/lib/generators/trek/templates/public_accounts/web/views/passwords/edit.html.slim.tt +25 -0
- data/lib/generators/trek/templates/public_accounts/web/views/registrations/new.html.slim.tt +28 -0
- data/lib/generators/trek/templates/public_accounts/web/views/sessions/new.html.slim.tt +38 -0
- data/lib/generators/trek/templates/public_accounts/web/views/sign_in_codes/new.html.slim.tt +17 -0
- data/lib/generators/trek/templates/spec/jobs/mail_delivery_job_spec.rb +11 -0
- data/lib/generators/trek/templates/spec/mailers/admin_mailer_spec.rb +10 -0
- data/lib/generators/trek/templates/spec/requests/admin/fragments_spec.rb +1 -1
- data/lib/generators/trek/templates/spec/requests/admin/good_job_spec.rb +34 -0
- data/lib/generators/trek/templates/spec/requests/admin/pages_spec.rb +1 -1
- data/lib/generators/trek/templates/spec/requests/admin/panels_spec.rb +16 -0
- data/lib/generators/trek/templates/spec/requests/admin/password_renewal_spec.rb +2 -2
- data/lib/generators/trek/templates/spec/requests/admin/sign_in_lockout_spec.rb +1 -1
- data/lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb +2 -2
- data/lib/generators/trek/templates/views/admin/users/index.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.en.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.en.text.erb +1 -1
- data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.fr.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.fr.text.erb +1 -1
- data/lib/trek/csp_reports.rb +22 -4
- data/lib/trek/typography_formatter.rb +35 -4
- data/lib/trek/version.rb +1 -1
- data/package.json +12 -8
- data/trek.gemspec +1 -1
- data/vitest.config.mjs +24 -0
- data/yarn.lock +1004 -288
- metadata +90 -3
data/lib/generators/trek/templates/public_accounts/views/mailer/sign_in_locked.fr.html.slim.tt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
p Bonjour #{@<%= file_name %>},
|
|
2
|
+
p
|
|
3
|
+
| Plusieurs tentatives de connexion à #{t("application_name")} avec votre
|
|
4
|
+
adresse e-mail ont échoué d'affilée, avec un mauvais mot de passe. Pour
|
|
5
|
+
protéger votre compte, la connexion est suspendue quelques minutes, un
|
|
6
|
+
peu plus longtemps après chaque nouvel échec.
|
|
7
|
+
<% unless web? -%>
|
|
8
|
+
p
|
|
9
|
+
| Si c'était vous, patientez quelques minutes avant de réessayer, ou
|
|
10
|
+
choisissez un nouveau mot de passe depuis l'application.
|
|
11
|
+
<% else -%>
|
|
12
|
+
p
|
|
13
|
+
| Si c'était vous, patientez quelques minutes avant de réessayer, ou
|
|
14
|
+
=< link_to "choisissez un nouveau mot de passe",
|
|
15
|
+
@reset_url,
|
|
16
|
+
target: "_blank",
|
|
17
|
+
rel: "noopener"
|
|
18
|
+
| .
|
|
19
|
+
<% end -%>
|
|
20
|
+
p
|
|
21
|
+
| Si ce n'était pas vous, quelqu'un essaie peut-être de deviner votre mot
|
|
22
|
+
de passe : s'il est court ou que vous l'utilisez ailleurs,
|
|
23
|
+
choisissez-en un nouveau.
|
|
24
|
+
p L'équipe #{t("application_name")}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Bonjour <%%= @<%= file_name %> %>,
|
|
2
|
+
|
|
3
|
+
Plusieurs tentatives de connexion à <%%= t("application_name") %> avec votre adresse e-mail ont échoué d'affilée, avec un mauvais mot de passe. Pour protéger votre compte, la connexion est suspendue quelques minutes, un peu plus longtemps après chaque nouvel échec.
|
|
4
|
+
|
|
5
|
+
<% unless web? -%>
|
|
6
|
+
Si c'était vous, patientez quelques minutes avant de réessayer, ou choisissez un nouveau mot de passe depuis l'application.
|
|
7
|
+
<% else -%>
|
|
8
|
+
Si c'était vous, patientez quelques minutes avant de réessayer, ou choisissez un nouveau mot de passe :
|
|
9
|
+
<%%= @reset_url %>
|
|
10
|
+
<% end -%>
|
|
11
|
+
|
|
12
|
+
Si ce n'était pas vous, quelqu'un essaie peut-être de deviner votre mot de passe : s'il est court ou que vous l'utilisez ailleurs, choisissez-en un nouveau.
|
|
13
|
+
|
|
14
|
+
L'équipe <%%= t("application_name") %>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Signs <%= human_name.pluralize.downcase %> in on the site, through a signed <%= file_name %>_session_id cookie: apart from
|
|
2
|
+
# the admin's session_id cookie (Trek::Authentication), so signing in or out of one never touches
|
|
3
|
+
# the other. Included in ApplicationController, so every page may show who is signed in; the pages
|
|
4
|
+
# that need a <%= human_name.downcase %> call `before_action :require_<%= file_name %>`.
|
|
5
|
+
module <%= accounts_module %>::Authentication
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
COOKIE = :<%= file_name %>_session_id
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
after_action :track_<%= file_name %>_activity
|
|
12
|
+
helper_method :current_<%= file_name %>, :<%= file_name %>_signed_in?, :staff_user
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def current_<%= file_name %>
|
|
18
|
+
resume_<%= file_name %>_session&.<%= file_name %>
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def <%= file_name %>_signed_in?
|
|
22
|
+
current_<%= file_name %>.present?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def require_<%= file_name %>
|
|
26
|
+
return if resume_<%= file_name %>_session
|
|
27
|
+
|
|
28
|
+
session[:return_to_after_<%= file_name %>_sign_in] = request.url if request.get?
|
|
29
|
+
redirect_to <%= route_prefix %>_sign_in_path, notice: t("<%= accounts_path %>.authentication.sign_in_needed")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Changing the password or deleting the account asks to sign in again when the session isn't recent.
|
|
33
|
+
def require_recent_<%= file_name %>_sign_in
|
|
34
|
+
return if Current.<%= file_name %>_session.recent?
|
|
35
|
+
|
|
36
|
+
session[:return_to_after_<%= file_name %>_sign_in] = request.referer || <%= route_prefix %>_account_url
|
|
37
|
+
redirect_to <%= route_prefix %>_sign_in_path, notice: t("<%= accounts_path %>.authentication.recent_sign_in_needed")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def resume_<%= file_name %>_session
|
|
41
|
+
return Current.<%= file_name %>_session if Current.<%= file_name %>_session
|
|
42
|
+
return unless cookies.signed[COOKIE]
|
|
43
|
+
|
|
44
|
+
# A session past its idle timeout or lifetime is dropped: the <%= human_name.downcase %> signs in again.
|
|
45
|
+
<%= file_name %>_session = <%= class_name %>::Session.find_by(id: cookies.signed[COOKIE])
|
|
46
|
+
return Current.<%= file_name %>_session = <%= file_name %>_session unless <%= file_name %>_session&.expired?
|
|
47
|
+
|
|
48
|
+
<%= file_name %>_session.destroy
|
|
49
|
+
cookies.delete(COOKIE)
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# A new session row and cookie: nothing about the sign-in lives in Rails' own session, which the
|
|
54
|
+
# admin's sign-in uses too.
|
|
55
|
+
def start_<%= file_name %>_session_for(<%= file_name %>)
|
|
56
|
+
<%= file_name %>.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |<%= file_name %>_session|
|
|
57
|
+
Current.<%= file_name %>_session = <%= file_name %>_session
|
|
58
|
+
Trek::ResolveSessionHostnameJob.perform_later(<%= file_name %>_session)
|
|
59
|
+
cookies.signed[COOKIE] = {
|
|
60
|
+
value: <%= file_name %>_session.id, expires: <%= class_name %>::Session.lifetime.from_now, httponly: true, same_site: :lax
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def after_<%= file_name %>_sign_in_url
|
|
66
|
+
session.delete(:return_to_after_<%= file_name %>_sign_in) || <%= route_prefix %>_account_url
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def terminate_<%= file_name %>_session
|
|
70
|
+
resume_<%= file_name %>_session&.destroy
|
|
71
|
+
Current.<%= file_name %>_session = nil
|
|
72
|
+
cookies.delete(COOKIE)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# At most once every <%= class_name %>::Session.activity_interval (5 minutes), and only on the requests
|
|
76
|
+
# that looked the <%= human_name.downcase %> up.
|
|
77
|
+
def track_<%= file_name %>_activity
|
|
78
|
+
<%= file_name %>_session = Current.<%= file_name %>_session
|
|
79
|
+
return unless <%= file_name %>_session&.persisted?
|
|
80
|
+
|
|
81
|
+
<%= file_name %>_session.record_activity
|
|
82
|
+
<%= file_name %> = <%= file_name %>_session.<%= file_name %>
|
|
83
|
+
return if <%= file_name %>.last_activity_at&.after?(<%= class_name %>::Session.activity_interval.ago)
|
|
84
|
+
|
|
85
|
+
<%= file_name %>.update_columns(last_activity_at: Time.current) # rubocop:disable Rails/SkipsModelValidations
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# The staff member signed in to the admin in this browser, if any, for previews and links to the
|
|
89
|
+
# admin on the site. Read only: it neither extends nor ends their admin session, and it never
|
|
90
|
+
# signs a <%= human_name.downcase %> in.
|
|
91
|
+
def staff_user
|
|
92
|
+
return @staff_user if defined?(@staff_user)
|
|
93
|
+
|
|
94
|
+
staff_session = User::Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id]
|
|
95
|
+
@staff_user = staff_session.user if staff_session && !staff_session.expired? && staff_session.user.privileged?
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# The <%= human_name.downcase %>'s own account: its name, email, signed-in browsers, and deleting it.
|
|
2
|
+
class <%= controllers_module %>::AccountsController < <%= controllers_module %>::BaseController
|
|
3
|
+
before_action :require_<%= file_name %>
|
|
4
|
+
before_action :require_recent_<%= file_name %>_sign_in, only: :destroy
|
|
5
|
+
before_action :set_<%= file_name %>
|
|
6
|
+
|
|
7
|
+
helper Trek::UserAgentsHelper
|
|
8
|
+
|
|
9
|
+
def show
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def update
|
|
13
|
+
if @<%= file_name %>.update(params.expect(<%= file_name %>: [:name]))
|
|
14
|
+
redirect_to <%= route_prefix %>_account_path, notice: t(".success")
|
|
15
|
+
else
|
|
16
|
+
render :show, status: :unprocessable_content
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def destroy
|
|
21
|
+
@<%= file_name %>.destroy!
|
|
22
|
+
Current.<%= file_name %>_session = nil
|
|
23
|
+
cookies.delete(<%= accounts_module %>::Authentication::COOKIE)
|
|
24
|
+
redirect_to "/", notice: t(".success"), status: :see_other
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def set_<%= file_name %>
|
|
30
|
+
@<%= file_name %> = Current.<%= file_name %>
|
|
31
|
+
@sessions = @<%= file_name %>.sessions.active.recent_first
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/generators/trek/templates/public_accounts/web/controllers/active_sessions_controller.rb.tt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Signs out browsers signed in as the current <%= human_name.downcase %>, listed on their account page. Only
|
|
2
|
+
# theirs: every query goes through Current.<%= file_name %>.sessions.
|
|
3
|
+
class <%= controllers_module %>::ActiveSessionsController < <%= controllers_module %>::BaseController
|
|
4
|
+
before_action :require_<%= file_name %>
|
|
5
|
+
|
|
6
|
+
def destroy
|
|
7
|
+
Current.<%= file_name %>.sessions.find(params[:id]).destroy
|
|
8
|
+
redirect_to <%= route_prefix %>_account_path(anchor: "sessions"), notice: t(".success"), status: :see_other
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def destroy_others
|
|
12
|
+
Current.<%= file_name %>.sessions.where.not(id: Current.<%= file_name %>_session.id).destroy_all
|
|
13
|
+
redirect_to <%= route_prefix %>_account_path(anchor: "sessions"), notice: t(".success"), status: :see_other
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# The account pages' controllers: <%= human_name.pluralize.downcase %> only, never the admin's users.
|
|
2
|
+
class <%= controllers_module %>::BaseController < ApplicationController
|
|
3
|
+
# Sign-ins, <% if code? %>code requests<% else %>sign-ups, password resets<% end %> and account changes go to the audit log, when the app has one.
|
|
4
|
+
include Trek::Auditable if defined?(::AuditLog)
|
|
5
|
+
|
|
6
|
+
# Emails sent to one address (<% if code? %>codes<% else %>password resets, confirmations<% end %>), whatever the IP address: rate_limit
|
|
7
|
+
# counts per IP address.
|
|
8
|
+
EMAILS_PER_ADDRESS = 5
|
|
9
|
+
EMAILS_PER_ADDRESS_WINDOW = 1.hour
|
|
10
|
+
|
|
11
|
+
# Failed sign-ins from one IP address, whatever the account, over a longer window than rate_limit's.
|
|
12
|
+
FAILED_SIGN_INS_PER_IP = 20
|
|
13
|
+
FAILED_SIGN_INS_WINDOW = 1.hour
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def normalized_email(email)
|
|
18
|
+
<%= class_name %>.normalize_value_for(:email, email.to_s)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def valid_email?(email)
|
|
22
|
+
email.match?(URI::MailTo::EMAIL_REGEXP)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Counted in Rails.cache, as rate_limit is, under a digest rather than the email itself.
|
|
26
|
+
def too_many_emails_to?(email)
|
|
27
|
+
key = "<%= accounts_path %>/emails/#{Digest::SHA256.hexdigest(email)}"
|
|
28
|
+
Rails.cache.increment(key, 1, expires_in: EMAILS_PER_ADDRESS_WINDOW).to_i > EMAILS_PER_ADDRESS
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def too_many_failed_sign_ins_from_ip?
|
|
32
|
+
Rails.cache.read(failed_sign_ins_from_ip_key).to_i >= FAILED_SIGN_INS_PER_IP
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def count_failed_sign_in_from_ip
|
|
36
|
+
Rails.cache.increment(failed_sign_ins_from_ip_key, 1, expires_in: FAILED_SIGN_INS_WINDOW)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def failed_sign_ins_from_ip_key
|
|
40
|
+
"<%= accounts_path %>/failed_sign_ins/#{request.remote_ip}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# For the audit log (Trek::Auditable): the signed-in <%= human_name.downcase %>, nil before signing in
|
|
44
|
+
def audit_actor
|
|
45
|
+
Current.<%= file_name %>
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The account signed in to, tried, or changed
|
|
49
|
+
def audit_target
|
|
50
|
+
@<%= file_name %> || Current.<%= file_name %>
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/generators/trek/templates/public_accounts/web/controllers/email_changes_controller.rb.tt
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<% if code? -%>
|
|
2
|
+
# Changing the account's email: a code sent to the new address confirms it's theirs.
|
|
3
|
+
<% else -%>
|
|
4
|
+
# Changing the account's email: a link sent to the new address confirms it's theirs.
|
|
5
|
+
<% end -%>
|
|
6
|
+
class <%= controllers_module %>::EmailChangesController < <%= controllers_module %>::BaseController
|
|
7
|
+
rate_limit to: 10, within: 15.minutes, only: %i[create<% if code? %> update<% end %>], with: -> { redirect_to <%= route_prefix %>_account_path, alert: t("<%= accounts_path %>.authentication.rate_limited") }
|
|
8
|
+
|
|
9
|
+
before_action :require_<%= file_name %><% if password? %>, except: :show<% end %>
|
|
10
|
+
<% if code? -%>
|
|
11
|
+
before_action :set_email, only: %i[edit update]
|
|
12
|
+
<% end -%>
|
|
13
|
+
|
|
14
|
+
def new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create
|
|
18
|
+
@email = normalized_email(params[:email])
|
|
19
|
+
return render_email_form t(".invalid_email") unless valid_email?(@email) && @email != Current.<%= file_name %>.email
|
|
20
|
+
return render_email_form t("<%= accounts_path %>.authentication.rate_limited") if too_many_emails_to?(@email)
|
|
21
|
+
|
|
22
|
+
<% if code? -%>
|
|
23
|
+
<%= accounts_module %>::SignInCodeJob.perform_later(@email, purpose: "email_change", <%= file_name %>: Current.<%= file_name %>)
|
|
24
|
+
session[:<%= file_name %>_new_email] = @email
|
|
25
|
+
redirect_to edit_<%= route_prefix %>_email_change_path, notice: t(".sent", email: @email)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def edit
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Taken by another account is only told once the code proved the address is theirs.
|
|
32
|
+
def update
|
|
33
|
+
return render_code_form t("<%= accounts_path %>.authentication.rate_limited") if too_many_failed_sign_ins_from_ip?
|
|
34
|
+
|
|
35
|
+
unless <%= class_name %>::SignInCode.redeem(@email, params[:otp], purpose: "email_change", <%= file_name %>: Current.<%= file_name %>)
|
|
36
|
+
count_failed_sign_in_from_ip
|
|
37
|
+
return render_code_form t(".failure")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
session.delete(:<%= file_name %>_new_email)
|
|
41
|
+
if Current.<%= file_name %>.update(email: @email)
|
|
42
|
+
redirect_to <%= route_prefix %>_account_path, notice: t(".success")
|
|
43
|
+
else
|
|
44
|
+
redirect_to <%= route_prefix %>_account_path, alert: t(".taken")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
<% else -%>
|
|
48
|
+
Current.<%= file_name %>.update_column(:unconfirmed_email, @email) # rubocop:disable Rails/SkipsModelValidations -- checked above
|
|
49
|
+
<%= class_name %>Mailer.email_change(Current.<%= file_name %>).deliver_later
|
|
50
|
+
redirect_to <%= route_prefix %>_account_path, notice: t(".sent", email: @email)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Opened from the email, possibly in another browser: the token is enough. Taken by another
|
|
54
|
+
# account is only told once the link proved the address is theirs.
|
|
55
|
+
def show
|
|
56
|
+
@<%= file_name %> = <%= class_name %>.find_by_token_for(:email_change, params[:token].to_s)
|
|
57
|
+
return redirect_to "/", alert: t(".invalid") unless @<%= file_name %>
|
|
58
|
+
|
|
59
|
+
if @<%= file_name %>.change_email!
|
|
60
|
+
redirect_to <%= file_name %>_signed_in? ? <%= route_prefix %>_account_path : <%= route_prefix %>_sign_in_path, notice: t(".success")
|
|
61
|
+
else
|
|
62
|
+
redirect_to "/", alert: t(".taken")
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
<% end -%>
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
<% if code? -%>
|
|
69
|
+
|
|
70
|
+
def set_email
|
|
71
|
+
@email = session[:<%= file_name %>_new_email]
|
|
72
|
+
redirect_to new_<%= route_prefix %>_email_change_path unless @email
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def render_code_form(alert)
|
|
76
|
+
flash.now[:alert] = alert
|
|
77
|
+
render :edit, status: :unprocessable_content
|
|
78
|
+
end
|
|
79
|
+
<% end -%>
|
|
80
|
+
|
|
81
|
+
def render_email_form(alert)
|
|
82
|
+
flash.now[:alert] = alert
|
|
83
|
+
render :new, status: :unprocessable_content
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# The link confirming the account's email (show), and sending it again (create).
|
|
2
|
+
class <%= controllers_module %>::EmailConfirmationsController < <%= controllers_module %>::BaseController
|
|
3
|
+
before_action :require_<%= file_name %>, only: :create
|
|
4
|
+
|
|
5
|
+
# Opened from the email, possibly in another browser: the token is enough.
|
|
6
|
+
def show
|
|
7
|
+
@<%= file_name %> = <%= class_name %>.find_by_token_for(:email_confirmation, params[:token].to_s)
|
|
8
|
+
return redirect_to "/", alert: t(".invalid") unless @<%= file_name %>
|
|
9
|
+
|
|
10
|
+
@<%= file_name %>.confirm_email!
|
|
11
|
+
redirect_to <%= file_name %>_signed_in? ? <%= route_prefix %>_account_path : <%= route_prefix %>_sign_in_path, notice: t(".success")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create
|
|
15
|
+
<%= file_name %> = Current.<%= file_name %>
|
|
16
|
+
<%= class_name %>Mailer.email_confirmation(<%= file_name %>).deliver_later unless <%= file_name %>.confirmed? || too_many_emails_to?(<%= file_name %>.email)
|
|
17
|
+
redirect_to <%= route_prefix %>_account_path, notice: t(".sent", email: <%= file_name %>.email)
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/generators/trek/templates/public_accounts/web/controllers/password_resets_controller.rb.tt
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# A forgotten password: a link emailed to the account, if the email has one, with the same answer
|
|
2
|
+
# either way, then the new password, which signs out every session.
|
|
3
|
+
class <%= controllers_module %>::PasswordResetsController < <%= controllers_module %>::BaseController
|
|
4
|
+
rate_limit to: 10, within: 15.minutes, only: %i[create update], with: -> { redirect_to new_<%= route_prefix %>_password_reset_path, alert: t("<%= accounts_path %>.authentication.rate_limited") }
|
|
5
|
+
|
|
6
|
+
before_action :set_<%= file_name %>_by_token, only: %i[edit update]
|
|
7
|
+
|
|
8
|
+
def new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def create
|
|
12
|
+
email = normalized_email(params[:email])
|
|
13
|
+
@<%= file_name %> = <%= class_name %>.find_by(email:) if valid_email?(email)
|
|
14
|
+
<%= class_name %>Mailer.password_reset(@<%= file_name %>).deliver_later if @<%= file_name %> && !too_many_emails_to?(email)
|
|
15
|
+
|
|
16
|
+
redirect_to <%= route_prefix %>_sign_in_path, notice: t(".sent")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def edit
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update
|
|
23
|
+
@<%= file_name %>.password = params.expect(<%= file_name %>: [:password])[:password].to_s
|
|
24
|
+
|
|
25
|
+
if @<%= file_name %>.save
|
|
26
|
+
@<%= file_name %>.sessions.delete_all
|
|
27
|
+
redirect_to <%= route_prefix %>_sign_in_path, notice: t(".success")
|
|
28
|
+
else
|
|
29
|
+
render :edit, status: :unprocessable_content
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# The token of the link, used up by the new password
|
|
36
|
+
def set_<%= file_name %>_by_token
|
|
37
|
+
@token = params[:token].to_s
|
|
38
|
+
@<%= file_name %> = <%= class_name %>.find_by_token_for(:password_reset, @token)
|
|
39
|
+
redirect_to new_<%= route_prefix %>_password_reset_path, alert: t("<%= accounts_path %>.password_resets.invalid") unless @<%= file_name %>
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changing the account's password, after a recent sign-in; a forgotten one goes through
|
|
2
|
+
# PasswordResetsController.
|
|
3
|
+
class <%= controllers_module %>::PasswordsController < <%= controllers_module %>::BaseController
|
|
4
|
+
before_action :require_<%= file_name %>
|
|
5
|
+
before_action :require_recent_<%= file_name %>_sign_in
|
|
6
|
+
|
|
7
|
+
def edit
|
|
8
|
+
@<%= file_name %> = Current.<%= file_name %>
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Signs out the account's other browsers: whoever knew the old password is out.
|
|
12
|
+
def update
|
|
13
|
+
@<%= file_name %> = Current.<%= file_name %>
|
|
14
|
+
@<%= file_name %>.password = params.expect(<%= file_name %>: [:password])[:password].to_s
|
|
15
|
+
|
|
16
|
+
if @<%= file_name %>.save
|
|
17
|
+
@<%= file_name %>.sessions.where.not(id: Current.<%= file_name %>_session.id).delete_all
|
|
18
|
+
redirect_to <%= route_prefix %>_account_path, notice: t(".success")
|
|
19
|
+
else
|
|
20
|
+
render :edit, status: :unprocessable_content
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/generators/trek/templates/public_accounts/web/controllers/registrations_controller.rb.tt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Signing up with an email and a password: signed in at once, the email confirmed later by a link
|
|
2
|
+
# (confirmed_at), so the app decides what an unconfirmed account may do. An email that already has
|
|
3
|
+
# an account is refused by its uniqueness validation, as on most sites.
|
|
4
|
+
class <%= controllers_module %>::RegistrationsController < <%= controllers_module %>::BaseController
|
|
5
|
+
rate_limit to: 5, within: 15.minutes, only: :create, with: -> { redirect_to <%= route_prefix %>_sign_up_path, alert: t("<%= accounts_path %>.authentication.rate_limited") }
|
|
6
|
+
|
|
7
|
+
def new
|
|
8
|
+
return redirect_to after_<%= file_name %>_sign_in_url if <%= file_name %>_signed_in?
|
|
9
|
+
|
|
10
|
+
@<%= file_name %> = <%= class_name %>.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create
|
|
14
|
+
@<%= file_name %> = <%= class_name %>.new(params.expect(<%= file_name %>: %i[email name password]))
|
|
15
|
+
|
|
16
|
+
if @<%= file_name %>.save
|
|
17
|
+
<%= class_name %>Mailer.email_confirmation(@<%= file_name %>).deliver_later
|
|
18
|
+
start_<%= file_name %>_session_for @<%= file_name %>
|
|
19
|
+
redirect_to <%= route_prefix %>_account_path, notice: t(".success", email: @<%= file_name %>.email)
|
|
20
|
+
else
|
|
21
|
+
render :new, status: :unprocessable_content
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<% if code? -%>
|
|
2
|
+
# Step 2 of signing in: the emailed code, which signs in to the email's account, created if it had
|
|
3
|
+
# none. Also signs out.
|
|
4
|
+
<% else -%>
|
|
5
|
+
# Signing in with the email and password, and out.
|
|
6
|
+
<% end -%>
|
|
7
|
+
class <%= controllers_module %>::SessionsController < <%= controllers_module %>::BaseController
|
|
8
|
+
rate_limit to: <%= code? ? 10 : 5 %>, within: 3.minutes, only: :create, with: -> { render_sign_in_form t("<%= accounts_path %>.authentication.rate_limited"), status: :too_many_requests }
|
|
9
|
+
|
|
10
|
+
<% if code? -%>
|
|
11
|
+
before_action :set_email, only: %i[new create]
|
|
12
|
+
|
|
13
|
+
def new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create
|
|
17
|
+
return render_sign_in_form t("<%= accounts_path %>.authentication.rate_limited"), status: :too_many_requests if too_many_failed_sign_ins_from_ip?
|
|
18
|
+
|
|
19
|
+
if <%= class_name %>::SignInCode.redeem(@email, params[:otp])
|
|
20
|
+
@<%= file_name %> = <%= class_name %>.for_verified_email!(@email)
|
|
21
|
+
session.delete(:<%= file_name %>_sign_in_email)
|
|
22
|
+
url = after_<%= file_name %>_sign_in_url
|
|
23
|
+
start_<%= file_name %>_session_for @<%= file_name %>
|
|
24
|
+
redirect_to url, notice: t(".success")
|
|
25
|
+
else
|
|
26
|
+
count_failed_sign_in_from_ip
|
|
27
|
+
render_sign_in_form t(".failure"), status: :unprocessable_content
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
<% else -%>
|
|
31
|
+
def new
|
|
32
|
+
redirect_to after_<%= file_name %>_sign_in_url if <%= file_name %>_signed_in?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def create
|
|
36
|
+
return render_sign_in_form t("<%= accounts_path %>.authentication.rate_limited"), status: :too_many_requests if too_many_failed_sign_ins_from_ip?
|
|
37
|
+
|
|
38
|
+
# Hashes the password typed even when no account matches, so the response takes as long
|
|
39
|
+
<%= file_name %> = <%= class_name %>.authenticate_by(email: params[:email].to_s, password: params[:password].to_s)
|
|
40
|
+
@<%= file_name %> = <%= file_name %> || <%= class_name %>.find_by(email: params[:email].to_s)
|
|
41
|
+
|
|
42
|
+
# A locked account is refused even with the right password, with the same answer as a wrong one
|
|
43
|
+
if <%= file_name %> && !<%= file_name %>.sign_in_locked?
|
|
44
|
+
<%= file_name %>.record_successful_sign_in!
|
|
45
|
+
url = after_<%= file_name %>_sign_in_url
|
|
46
|
+
start_<%= file_name %>_session_for <%= file_name %>
|
|
47
|
+
redirect_to url, notice: t(".success")
|
|
48
|
+
else
|
|
49
|
+
@<%= file_name %>&.record_failed_sign_in!
|
|
50
|
+
count_failed_sign_in_from_ip
|
|
51
|
+
render_sign_in_form t(".failure"), status: :unprocessable_content
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
<% end -%>
|
|
55
|
+
|
|
56
|
+
def destroy
|
|
57
|
+
terminate_<%= file_name %>_session
|
|
58
|
+
redirect_to "/", notice: t(".success"), status: :see_other
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
<% if code? -%>
|
|
63
|
+
|
|
64
|
+
# The email of step 1, kept in Rails' session rather than the URL
|
|
65
|
+
def set_email
|
|
66
|
+
@email = session[:<%= file_name %>_sign_in_email]
|
|
67
|
+
redirect_to <%= route_prefix %>_sign_in_path unless @email
|
|
68
|
+
end
|
|
69
|
+
<% end -%>
|
|
70
|
+
|
|
71
|
+
def render_sign_in_form(alert, status:)
|
|
72
|
+
<% if password? -%>
|
|
73
|
+
@email = params[:email]
|
|
74
|
+
<% end -%>
|
|
75
|
+
flash.now[:alert] = alert
|
|
76
|
+
render :new, status:
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/generators/trek/templates/public_accounts/web/controllers/sign_in_codes_controller.rb.tt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Step 1 of signing in: an email, to which a code is sent. Whether or not it has an account, the
|
|
2
|
+
# answer is the same; the code creates the account on its first use.
|
|
3
|
+
class <%= controllers_module %>::SignInCodesController < <%= controllers_module %>::BaseController
|
|
4
|
+
rate_limit to: 10, within: 15.minutes, only: :create, with: -> { render_email_form t("<%= accounts_path %>.authentication.rate_limited"), status: :too_many_requests }
|
|
5
|
+
|
|
6
|
+
def new
|
|
7
|
+
redirect_to after_<%= file_name %>_sign_in_url if <%= file_name %>_signed_in?
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create
|
|
11
|
+
email = normalized_email(params[:email])
|
|
12
|
+
return render_email_form t(".invalid_email"), status: :unprocessable_content unless valid_email?(email)
|
|
13
|
+
return render_email_form t("<%= accounts_path %>.authentication.rate_limited"), status: :too_many_requests if too_many_emails_to?(email)
|
|
14
|
+
|
|
15
|
+
<%= accounts_module %>::SignInCodeJob.perform_later(email)
|
|
16
|
+
session[:<%= file_name %>_sign_in_email] = email
|
|
17
|
+
redirect_to <%= route_prefix %>_sign_in_code_path, notice: t(".sent", email:)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def render_email_form(alert, status:)
|
|
23
|
+
@email = params[:email]
|
|
24
|
+
flash.now[:alert] = alert
|
|
25
|
+
render :new, status:
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
= render "<%= accounts_path %>/flash"
|
|
2
|
+
|
|
3
|
+
h1 = t(".title")
|
|
4
|
+
|
|
5
|
+
= form_with model: @<%= file_name %>, url: <%= route_prefix %>_account_path, method: :patch do |f|
|
|
6
|
+
- if @<%= file_name %>.errors.any?
|
|
7
|
+
ul(role="alert")
|
|
8
|
+
- @<%= file_name %>.errors.full_messages.each do |message|
|
|
9
|
+
li = message
|
|
10
|
+
= f.label :name
|
|
11
|
+
= f.text_field :name, autocomplete: "name"
|
|
12
|
+
= f.submit t(".save")
|
|
13
|
+
|
|
14
|
+
h2 = <%= class_name %>.human_attribute_name(:email)
|
|
15
|
+
p
|
|
16
|
+
= @<%= file_name %>.email
|
|
17
|
+
=< link_to t(".change_email"), new_<%= route_prefix %>_email_change_path
|
|
18
|
+
<% if password? -%>
|
|
19
|
+
- unless @<%= file_name %>.confirmed?
|
|
20
|
+
p = t(".unconfirmed")
|
|
21
|
+
= button_to t(".resend_confirmation"), <%= route_prefix %>_email_confirmation_path
|
|
22
|
+
- if @<%= file_name %>.unconfirmed_email
|
|
23
|
+
p = t(".pending_email", email: @<%= file_name %>.unconfirmed_email)
|
|
24
|
+
|
|
25
|
+
h2 = <%= class_name %>.human_attribute_name(:password)
|
|
26
|
+
p = link_to t(".change_password"), edit_<%= route_prefix %>_password_path
|
|
27
|
+
<% end -%>
|
|
28
|
+
|
|
29
|
+
h2#sessions = t(".sessions")
|
|
30
|
+
ul
|
|
31
|
+
- @sessions.each do |<%= file_name %>_session|
|
|
32
|
+
li
|
|
33
|
+
= user_agent_summary(<%= file_name %>_session.user_agent)
|
|
34
|
+
=< t(".signed_in", date: l(<%= file_name %>_session.created_at, format: :long))
|
|
35
|
+
- if <%= file_name %>_session == Current.<%= file_name %>_session
|
|
36
|
+
=< t(".this_browser")
|
|
37
|
+
- else
|
|
38
|
+
= button_to t(".sign_out_session"),
|
|
39
|
+
<%= route_prefix %>_active_session_path(<%= file_name %>_session),
|
|
40
|
+
method: :delete
|
|
41
|
+
- if @sessions.size > 1
|
|
42
|
+
= button_to t(".sign_out_others"),
|
|
43
|
+
others_<%= route_prefix %>_active_sessions_path,
|
|
44
|
+
method: :delete
|
|
45
|
+
|
|
46
|
+
= button_to t(".sign_out"), <%= route_prefix %>_sign_out_path, method: :delete
|
|
47
|
+
|
|
48
|
+
h2 = t(".delete_title")
|
|
49
|
+
p = t(".delete_intro")
|
|
50
|
+
= button_to t(".delete"), <%= route_prefix %>_account_path,
|
|
51
|
+
method: :delete,
|
|
52
|
+
form: { data: { turbo_confirm: t(".delete_confirm") } }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
= render "<%= accounts_path %>/flash"
|
|
2
|
+
|
|
3
|
+
h1 = t(".title")
|
|
4
|
+
p = t(".intro", email: @email)
|
|
5
|
+
|
|
6
|
+
= form_with url: <%= route_prefix %>_email_change_path, method: :patch do |f|
|
|
7
|
+
= f.label :otp, t(".code")
|
|
8
|
+
= f.text_field :otp,
|
|
9
|
+
required: true,
|
|
10
|
+
autofocus: true,
|
|
11
|
+
autocomplete: "one-time-code",
|
|
12
|
+
inputmode: "numeric",
|
|
13
|
+
pattern: "[0-9 ]*",
|
|
14
|
+
maxlength: 7
|
|
15
|
+
= f.submit t(".submit")
|
|
16
|
+
|
|
17
|
+
p = link_to t(".back"), <%= route_prefix %>_account_path
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
= render "<%= accounts_path %>/flash"
|
|
2
|
+
|
|
3
|
+
h1 = t(".title")
|
|
4
|
+
p = t(".intro")
|
|
5
|
+
|
|
6
|
+
= form_with url: <%= route_prefix %>_email_change_path do |f|
|
|
7
|
+
= f.label :email, t(".email")
|
|
8
|
+
= f.email_field :email,
|
|
9
|
+
value: @email,
|
|
10
|
+
required: true,
|
|
11
|
+
autofocus: true,
|
|
12
|
+
autocomplete: "email"
|
|
13
|
+
= f.submit t(".submit")
|
|
14
|
+
|
|
15
|
+
p = link_to t(".back"), <%= route_prefix %>_account_path
|
data/lib/generators/trek/templates/public_accounts/web/views/password_resets/edit.html.slim.tt
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
= render "<%= accounts_path %>/flash"
|
|
2
|
+
|
|
3
|
+
h1 = t(".title")
|
|
4
|
+
|
|
5
|
+
= form_with model: @<%= file_name %>,
|
|
6
|
+
url: <%= route_prefix %>_password_reset_path,
|
|
7
|
+
method: :patch do |f|
|
|
8
|
+
- if @<%= file_name %>.errors.any?
|
|
9
|
+
ul(role="alert")
|
|
10
|
+
- @<%= file_name %>.errors.full_messages.each do |message|
|
|
11
|
+
li = message
|
|
12
|
+
= hidden_field_tag :token, @token
|
|
13
|
+
= f.label :password
|
|
14
|
+
ruby:
|
|
15
|
+
rules = {
|
|
16
|
+
min: @<%= file_name %>.password_min_length,
|
|
17
|
+
passphrase: @<%= file_name %>.passphrase_length
|
|
18
|
+
}
|
|
19
|
+
p = t(".rules", **rules)
|
|
20
|
+
= f.password_field :password,
|
|
21
|
+
required: true,
|
|
22
|
+
autofocus: true,
|
|
23
|
+
autocomplete: "new-password",
|
|
24
|
+
maxlength: 72,
|
|
25
|
+
minlength: @<%= file_name %>.password_min_length
|
|
26
|
+
= f.submit t(".submit")
|