trek 2.3.2 → 2.4.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 (165) hide show
  1. checksums.yaml +4 -4
  2. data/.node-version +1 -1
  3. data/.yarn/releases/yarn-4.18.1.cjs +1000 -0
  4. data/.yarnrc.yml +3 -5
  5. data/AGENTS.md +7 -4
  6. data/CHANGELOG.md +48 -0
  7. data/Gemfile.lock +35 -1
  8. data/Makefile +12 -0
  9. data/app/assets/icons/trek/passkey.svg +5 -0
  10. data/app/assets/stylesheets/trek/base.css +6 -0
  11. data/app/components/trek/entries/item_component/item_component.css +6 -0
  12. data/app/components/trek/form/image_field_component/image_field_component.html.slim +9 -0
  13. data/app/components/trek/form/password_strength_component/password_strength_component.css +56 -0
  14. data/app/components/trek/form/password_strength_component/password_strength_component.html.slim +8 -0
  15. data/app/components/trek/form/password_strength_component/password_strength_component.js +74 -0
  16. data/app/components/trek/form/password_strength_component.rb +29 -0
  17. data/app/components/trek/gate_component/gate_component.css +7 -0
  18. data/app/components/trek/icon/sprite_component.rb +3 -2
  19. data/app/components/trek/section_component/section_component.css +22 -0
  20. data/app/components/trek/section_component/section_component.html.slim +5 -0
  21. data/app/components/trek/section_component/section_component.js +1 -0
  22. data/app/components/trek/section_component.rb +14 -0
  23. data/app/controllers/concerns/trek/auditable.rb +103 -0
  24. data/app/controllers/concerns/trek/filters.rb +2 -0
  25. data/app/controllers/concerns/trek/passkeys.rb +37 -0
  26. data/app/controllers/trek/csp_reports_controller.rb +62 -0
  27. data/app/controllers/trek/resource_controller.rb +7 -1
  28. data/app/helpers/trek/audit_logs_helper.rb +21 -0
  29. data/app/helpers/trek/user_agents_helper.rb +36 -0
  30. data/app/javascript/controllers/index.js +3 -0
  31. data/app/javascript/controllers/passkey_controller.js +118 -0
  32. data/app/javascript/trek.js +4 -0
  33. data/app/jobs/trek/audit_log/purge_job.rb +18 -0
  34. data/app/jobs/trek/purge_expired_sessions_job.rb +14 -0
  35. data/app/jobs/trek/resolve_session_hostname_job.rb +17 -0
  36. data/app/models/concerns/trek/audit_log.rb +30 -0
  37. data/app/models/concerns/trek/users/passkeys.rb +75 -0
  38. data/app/models/concerns/trek/users/password_history.rb +51 -0
  39. data/app/models/concerns/trek/users/password_policy.rb +85 -0
  40. data/app/models/concerns/trek/users/password_renewal.rb +51 -0
  41. data/app/models/concerns/trek/users/session_lifetime.rb +39 -0
  42. data/app/models/concerns/trek/users/sign_in_lockout.rb +67 -0
  43. data/app/models/concerns/trek/users/sign_ins.rb +31 -0
  44. data/app/models/concerns/trek/users/validations.rb +3 -2
  45. data/app/models/trek/feature.rb +6 -0
  46. data/app/models/trek/pwned_passwords.rb +40 -0
  47. data/app/models/trek/user_session.rb +10 -0
  48. data/app/policies/trek/audit_log_policy.rb +34 -0
  49. data/app/policies/trek/user_policy.rb +5 -0
  50. data/app/validators/not_disposable_validator.rb +18 -0
  51. data/config/brakeman.ignore +22 -0
  52. data/config/i18n-tasks.yml +7 -0
  53. data/config/locales/trek.en.yml +144 -39
  54. data/config/locales/trek.fr.yml +145 -40
  55. data/docs/.vitepress/config.mjs +8 -3
  56. data/docs/deploy.md +2 -0
  57. data/docs/proxies.md +53 -0
  58. data/docs/reference/components.md +1 -0
  59. data/docs/reference/environment-variables.md +2 -0
  60. data/docs/reference/generators/admin-user.md +1 -1
  61. data/docs/reference/generators/audit-log.md +113 -0
  62. data/docs/reference/generators/install.md +12 -3
  63. data/docs/reference/generators/security-headers.md +109 -0
  64. data/docs/reference/index.md +3 -0
  65. data/docs/reference/models/tag.md +4 -2
  66. data/docs/reference/models/user.md +123 -3
  67. data/docs/reference/validators.md +94 -0
  68. data/docs/security.md +90 -0
  69. data/lefthook.yml +8 -0
  70. data/lib/generators/trek/install/admin/user_password_resets_generator.rb +5 -0
  71. data/lib/generators/trek/install/admin/user_sessions_generator.rb +5 -0
  72. data/lib/generators/trek/install/admin/users_generator.rb +23 -0
  73. data/lib/generators/trek/install/audit_log_generator.rb +56 -0
  74. data/lib/generators/trek/install/authentication_generator.rb +77 -3
  75. data/lib/generators/trek/install/esbuild_generator.rb +1 -2
  76. data/lib/generators/trek/install/mailer_generator.rb +18 -0
  77. data/lib/generators/trek/install/model_specs_generator.rb +6 -4
  78. data/lib/generators/trek/install/postcss_generator.rb +1 -4
  79. data/lib/generators/trek/install/security_headers_generator.rb +49 -0
  80. data/lib/generators/trek/install/yarn_generator.rb +1 -1
  81. data/lib/generators/trek/install_generator.rb +62 -54
  82. data/lib/generators/trek/taxonomies_generator.rb +5 -0
  83. data/lib/generators/trek/templates/.gitlab-ci.yml +18 -2
  84. data/lib/generators/trek/templates/controllers/admin/active_sessions_controller.rb +21 -0
  85. data/lib/generators/trek/templates/controllers/admin/audit_logs_controller.rb +30 -0
  86. data/lib/generators/trek/templates/controllers/admin/passkey_sessions_controller.rb +41 -0
  87. data/lib/generators/trek/templates/controllers/admin/passkeys_controller.rb +37 -0
  88. data/lib/generators/trek/templates/controllers/admin/user_password_resets_controller.rb +4 -4
  89. data/lib/generators/trek/templates/controllers/admin/user_sessions_controller.rb +53 -4
  90. data/lib/generators/trek/templates/controllers/admin/users_controller.rb +20 -0
  91. data/lib/generators/trek/templates/controllers/concerns/trek/authentication.rb +58 -7
  92. data/lib/generators/trek/templates/helpers/application_helper.rb +0 -4
  93. data/lib/generators/trek/templates/initializers/audit_log.rb +11 -0
  94. data/lib/generators/trek/templates/initializers/content_security_policy.rb +49 -0
  95. data/lib/generators/trek/templates/initializers/permissions_policy.rb +19 -0
  96. data/lib/generators/trek/templates/jobs/admin/password_reset_job.rb +9 -0
  97. data/lib/generators/trek/templates/layouts/admin/auth.html.slim +0 -5
  98. data/lib/generators/trek/templates/layouts/admin.html.slim +0 -3
  99. data/lib/generators/trek/templates/mailers/admin/passwords_mailer.rb +1 -1
  100. data/lib/generators/trek/templates/mailers/admin_mailer.rb +28 -1
  101. data/lib/generators/trek/templates/migrations/audit_logs.rb.erb +27 -0
  102. data/lib/generators/trek/templates/migrations/tags.rb.erb +6 -1
  103. data/lib/generators/trek/templates/migrations/user_passkeys.rb.erb +24 -0
  104. data/lib/generators/trek/templates/migrations/user_password_renewal.rb.erb +8 -0
  105. data/lib/generators/trek/templates/migrations/user_previous_passwords.rb.erb +10 -0
  106. data/lib/generators/trek/templates/migrations/user_session_tracking.rb.erb +11 -0
  107. data/lib/generators/trek/templates/migrations/user_sign_in_lockout.rb.erb +7 -0
  108. data/lib/generators/trek/templates/migrations/user_sign_ins.rb.erb +13 -0
  109. data/lib/generators/trek/templates/models/audit_log.rb +3 -0
  110. data/lib/generators/trek/templates/models/tag_category.rb +2 -1
  111. data/lib/generators/trek/templates/models/user/passkey.rb +17 -0
  112. data/lib/generators/trek/templates/models/user/previous_password.rb +4 -0
  113. data/lib/generators/trek/templates/models/user/session.rb +8 -0
  114. data/lib/generators/trek/templates/policies/audit_log.rb +4 -0
  115. data/lib/generators/trek/templates/spec/mailers/admin/passwords_mailer_spec.rb +16 -0
  116. data/lib/generators/trek/templates/spec/mailers/admin_mailer_spec.rb +50 -0
  117. data/lib/generators/trek/templates/spec/models/audit_log_spec.rb +43 -0
  118. data/lib/generators/trek/templates/spec/models/tag_spec.rb +52 -0
  119. data/lib/generators/trek/templates/spec/models/user/session_spec.rb +68 -0
  120. data/lib/generators/trek/templates/spec/models/user_spec.rb +142 -4
  121. data/lib/generators/trek/templates/spec/requests/admin/active_sessions_spec.rb +119 -0
  122. data/lib/generators/trek/templates/spec/requests/admin/audit_logs_spec.rb +149 -0
  123. data/lib/generators/trek/templates/spec/requests/admin/passkey_sessions_spec.rb +103 -0
  124. data/lib/generators/trek/templates/spec/requests/admin/passkeys_spec.rb +104 -0
  125. data/lib/generators/trek/templates/spec/requests/admin/password_renewal_spec.rb +83 -0
  126. data/lib/generators/trek/templates/spec/requests/admin/sign_in_lockout_spec.rb +95 -0
  127. data/lib/generators/trek/templates/spec/requests/admin/sign_in_notices_spec.rb +93 -0
  128. data/lib/generators/trek/templates/spec/requests/admin/user_password_resets_spec.rb +43 -6
  129. data/lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb +25 -2
  130. data/lib/generators/trek/templates/views/admin/audit_logs/index.html.slim +35 -0
  131. data/lib/generators/trek/templates/views/admin/audit_logs/show.html.slim +50 -0
  132. data/lib/generators/trek/templates/views/admin/user_password_resets/edit.html.slim +16 -6
  133. data/lib/generators/trek/templates/views/admin/user_password_resets/new.html.slim +2 -1
  134. data/lib/generators/trek/templates/views/admin/user_sessions/new.html.slim +27 -1
  135. data/lib/generators/trek/templates/views/admin/users/_passkeys.html.slim +67 -0
  136. data/lib/generators/trek/templates/views/admin/users/_properties.html.slim +21 -0
  137. data/lib/generators/trek/templates/views/admin/users/_sessions.html.slim +70 -0
  138. data/lib/generators/trek/templates/views/admin/users/index.html.slim +13 -0
  139. data/lib/generators/trek/templates/views/admin/users/show.html.slim +10 -21
  140. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.en.html.slim +22 -0
  141. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.en.text.erb +16 -0
  142. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.fr.html.slim +22 -0
  143. data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.fr.text.erb +16 -0
  144. data/lib/generators/trek/templates/views/admin_mailer/sign_in_locked.en.html.slim +17 -0
  145. data/lib/generators/trek/templates/views/admin_mailer/sign_in_locked.en.text.erb +10 -0
  146. data/lib/generators/trek/templates/views/admin_mailer/sign_in_locked.fr.html.slim +18 -0
  147. data/lib/generators/trek/templates/views/admin_mailer/sign_in_locked.fr.text.erb +10 -0
  148. data/lib/trek/admin_ip_allowlist.rb +52 -0
  149. data/lib/trek/csp_reports.rb +99 -0
  150. data/lib/trek/data/common_passwords.txt +857 -0
  151. data/lib/trek/data/passkey_providers.json +404 -0
  152. data/lib/trek/disposable_email_domains.rb +53 -0
  153. data/lib/trek/engine.rb +12 -0
  154. data/lib/trek/passkey_providers.rb +19 -0
  155. data/lib/trek/reverse_dns.rb +27 -0
  156. data/lib/trek/version.rb +1 -1
  157. data/lib/trek.rb +13 -0
  158. data/package.json +3 -5
  159. data/renovate.json +11 -1
  160. data/trek.gemspec +3 -0
  161. data/yarn.lock +1757 -2019
  162. metadata +132 -4
  163. data/.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs +0 -541
  164. data/.yarn/releases/yarn-3.8.0.cjs +0 -875
  165. data/lib/generators/trek/templates/models/user_session.rb +0 -3
data/docs/security.md ADDED
@@ -0,0 +1,90 @@
1
+ # Security and compliance
2
+
3
+ ISO 27001 certifies an organization's information security management system, and the NIS2 directive sets obligations for the "essential" and "important" entities of the sectors it lists. Neither applies to a library: using Trek doesn't make an app compliant, and no setting does. What Trek does is provide many of the technical measures these frameworks expect from an application, with defaults chosen for them.
4
+
5
+ This page lists those measures for an auditor or a security questionnaire, and what stays with the app, its hosting and the organization running it. It describes the current version; measures still to come are marked as planned.
6
+
7
+ ## ISO 27001 controls
8
+
9
+ The controls of ISO/IEC 27001:2022's Annex A that an application takes part in:
10
+
11
+ | Control | What Trek provides | Docs |
12
+ | --- | --- | --- |
13
+ | **5.15** Access control, **8.2** Privileged access rights, **8.3** Information access restriction | Roles (`admin`, `editor`…), only `admin` and `editor` can sign in to the admin, and policies authorize each record and attribute per role, only `admin` the audit log | [User › Roles](/reference/models/user#roles), [Policies](/reference/policies) |
14
+ | **5.17** Authentication information | The ANSSI's password recommendations for a privileged access: 12 characters or more, no common password, sequence, name or email; the last 5 passwords refused; optional expiry; an admin can ask everyone for a new password after a suspected compromise. Password reset links last 15 minutes, invitation links a week, and no column stores them | [Password policy](/reference/models/user#password-policy), [Previous passwords](/reference/models/user#previous-passwords) |
15
+ | **5.18** Access rights | Each user's last activity and previous sign-in. Planned: reviewing access, and deactivating dormant accounts | [User › Sign-ins](/reference/models/user#sign-ins) |
16
+ | **5.34** Privacy and protection of PII | The audit log records parameter names, not their values, and the docs set out the GDPR's conditions for keeping it. The breached password check, which would send part of a password's hash to an outside service, is off by default | [Audit log › Personal data](/reference/generators/audit-log#personal-data) |
17
+ | **8.5** Secure authentication | Passkeys, which can't be phished and count as two factors; failed sign-ins throttled per IP address and per account, with an email to the account's user; sign-in and password reset that don't reveal whether an account exists; sessions ending after 48 hours idle and 30 days in all, listed to their user, who can end them; an email on a sign-in from a new browser. Planned: TOTP codes, and requiring a second factor | [User](/reference/models/user#sign-in-and-password-resets) |
18
+ | **8.8** Management of technical vulnerabilities | Generated apps' CI runs Brakeman and bundler-audit, and can make a Software Bill of Materials (SBOM) of their gems and npm packages; Trek's own CI too, and Renovate keeps its dependencies up to date. Planned: a vulnerability disclosure policy | [Installer](/reference/generators/install), [Software Bill of Materials](/reference/generators/install#software-bill-of-materials) |
19
+ | **8.9** Configuration management | The installer gives every app the same configuration, security headers included: a Content Security Policy, report-only at first, and a `Permissions-Policy` turning off the browser features the app doesn't use | [Installer](/reference/generators/install), [Security headers](/reference/generators/security-headers) |
20
+ | **8.15** Logging | An audit log of every change in the admin and every sign-in: author, request, record, status, IP address. Rows can't be changed through the app, and are deleted after 6 months, as the CNIL recommends. Planned: sending them to a store outside the app | [Audit log](/reference/generators/audit-log) |
21
+ | **8.16** Monitoring activities | Errors and performance reported to AppSignal, and the Content Security Policy's violations; users emailed on a sign-in from a new browser and when failed sign-ins lock their account | [Installer](/reference/generators/install) |
22
+ | **8.20** Networks security | The visitor's real IP address behind proxies and CDNs, which the limits and logs rely on; the admin can be restricted to IP addresses and ranges (`ADMIN_ALLOWED_IPS`) | [Proxies](/proxies), [Restricting the admin to IP addresses](/proxies#restricting-the-admin-to-ip-addresses) |
23
+ | **8.24** Use of cryptography | HTTPS only (`force_ssl`, with HSTS and secure cookies), bcrypt password hashes, signed session cookies, signed tokens in emailed links. Planned: encrypting sensitive columns | [Cryptography](#cryptography) |
24
+ | **8.25** to **8.29** Secure development | Trek's changes go through merge requests, with specs, RuboCop, Brakeman and bundler-audit in CI. Generated apps get the same CI, and specs checking Trek's behaviour in them | [Installer](/reference/generators/install) |
25
+ | **8.33** Test information | Planned: anonymized production dumps for development | |
26
+
27
+ ## NIS2 measures
28
+
29
+ The measures of the directive's Article 21(2), and what Trek provides for each:
30
+
31
+ | Measure | What Trek provides |
32
+ | --- | --- |
33
+ | (a) Risk analysis and security policies | — |
34
+ | (b) Incident handling | Evidence: the audit log, AppSignal's errors, the sign-in emails |
35
+ | (c) Business continuity, backups, disaster recovery | — |
36
+ | (d) Supply chain security | Dependencies checked for known vulnerabilities in CI (bundler-audit), and kept up to date by Renovate. A CycloneDX [SBOM](/reference/generators/install#software-bill-of-materials) of an app, made in its CI, to hand to a client who asks |
37
+ | (e) Secure development and maintenance, vulnerability handling and disclosure | Changes through merge requests, with specs, RuboCop, Brakeman and bundler-audit in CI; generated apps get the same CI. Planned: a vulnerability disclosure policy |
38
+ | (f) Assessing the measures' effectiveness | — |
39
+ | (g) Cyber hygiene and training | — |
40
+ | (h) Cryptography and encryption | HTTPS only (`force_ssl`, with HSTS and secure cookies), bcrypt password hashes, signed session cookies, signed tokens in emailed links (see [Cryptography](#cryptography)). Planned: encrypting sensitive columns |
41
+ | (i) Human resources security, access control, asset management | Roles, and policies authorizing each record and attribute per role; sessions that expire and that their user can end. Planned: reviewing access, and deactivating dormant accounts |
42
+ | (j) Multi-factor authentication | Passkeys. Planned: TOTP codes, and requiring a second factor |
43
+
44
+ Measures (a), (c), (f) and (g) are up to the organization and its hosting (see [What Trek expects from the hosting](#what-trek-expects-from-the-hosting)).
45
+
46
+ Reporting a significant incident (Article 23: an early warning within 24 hours, a notification within 72 hours, a final report within a month) is up to the organization too; the audit log helps establish what happened.
47
+
48
+ ## Cryptography
49
+
50
+ The cryptography a generated app uses, with Rails 8.1's defaults: an inventory for ISO 27001's control 8.24, and for planning the move to post-quantum cryptography. No tool can make one automatically for a Ruby app, so this inventory is maintained by hand.
51
+
52
+ | Use | Algorithm | Against a quantum computer |
53
+ | --- | --- | --- |
54
+ | Passwords | bcrypt, cost 12 | Resists |
55
+ | Rails' session cookie (flash messages, CSRF token) | AES-256-GCM | Resists |
56
+ | Signed cookies: the session id, the browsers a user signed in from | HMAC-SHA1 | Resists |
57
+ | Password reset and invitation links (`generates_token_for`) | HMAC-SHA1 | Resists |
58
+ | Keys of the above, derived from `secret_key_base` | PBKDF2-HMAC-SHA256, 1,000 iterations | Resists |
59
+ | Passkeys (the [webauthn](https://github.com/cedarcode/webauthn-ruby) gem's defaults) | ECDSA P-256 (ES256), RSA (PS256, RS256) | Vulnerable, like all of today's public-key signatures; the move depends on browsers and authenticators |
60
+ | Breached password check (off by default) | The first 5 characters of the password's SHA-1, sent to Have I Been Pwned to keep the password unknown to it; not a security function | — |
61
+ | HTTPS | TLS, as configured where it ends (platform router, CDN) | Depends on the hosting |
62
+ | Planned: encrypting sensitive columns | AES-256-GCM (Active Record Encryption) | Resists |
63
+
64
+ HMAC-SHA1, Rails' default for signed cookies and tokens, isn't broken: an HMAC doesn't rely on SHA-1's collision resistance. The ANSSI advises against SHA-1 in new uses, though, and an audit following its guidance may question it.
65
+
66
+ ## What stays with the app, the hosting and the organization
67
+
68
+ - **The organization:** its information security management system, risk analysis, policies, training, incident response and reporting, and its suppliers' assessment, the hosting provider's included.
69
+ - **The app:** its own models and controllers, their policies, which data they hold, and keeping Trek and its other dependencies up to date.
70
+ - **The hosting:** backups, redundancy, the network, the servers' security, and its certifications.
71
+
72
+ ## What Trek expects from the hosting
73
+
74
+ - **Backups** of the database and of the uploads' bucket, encrypted, kept apart from the app, and restored now and then to check them.
75
+ - **HTTPS**, with the proxies in front of the app forwarding the original host, scheme and visitor's IP address (see [Proxies](/proxies)).
76
+ - **A cache store shared** by all the app's processes and servers, for the sign-in limits (see [Failed sign-ins](/reference/models/user#failed-sign-ins)).
77
+ - **An Active Job backend** running the scheduled purges: `Trek::PurgeExpiredSessionsJob`, and `Trek::AuditLog::PurgeJob` with the audit log.
78
+ - **Secrets out of the code:** the master key and API keys in the platform's environment variables, different in each environment.
79
+ - **Logs** kept for a limited time, as they hold IP addresses.
80
+
81
+ ## Recommended settings
82
+
83
+ For an app whose client is subject to ISO 27001 or NIS2:
84
+
85
+ - install the [audit log](/reference/generators/audit-log), and schedule its purge;
86
+ - encourage passkeys; for admins signing in with a password only, make it expire after a year (`self.password_expires_after = 1.year`, as the ANSSI suggests);
87
+ - keep the `admin` role to the few people who need it;
88
+ - enforce the Content Security Policy once its reports show no violation left but noise ([Report first, then enforce](/reference/generators/security-headers#report-first-then-enforce));
89
+ - list the CDN's ranges in `trusted_proxies` when there is one ([Proxies](/proxies));
90
+ - when the admins work from known networks (an office, a VPN), restrict the admin to them with `ADMIN_ALLOWED_IPS`.
data/lefthook.yml CHANGED
@@ -28,8 +28,16 @@ pre-commit:
28
28
  prettier:
29
29
  tags: css js style
30
30
  glob: "{app,config,lib,spec}/**/*.{css,js,json,md,yml}"
31
+ # The locale files have their own command, as two commands running in parallel on the same
32
+ # file would clash.
33
+ exclude: "config/locales/*.yml"
31
34
  run: yarn prettier {staged_files} --write
32
35
  stage_fixed: true
36
+ i18n_normalize:
37
+ tags: i18n style
38
+ glob: "config/locales/*.yml"
39
+ run: bundle exec i18n-tasks normalize && yarn prettier {staged_files} --write
40
+ stage_fixed: true
33
41
  slim_lint:
34
42
  tags: slim style
35
43
  glob: "**/*.slim"
@@ -17,6 +17,11 @@ module Trek
17
17
  "app/controllers/admin/user_password_resets_controller.rb"
18
18
  end
19
19
 
20
+ def copy_job
21
+ template "jobs/admin/password_reset_job.rb",
22
+ "app/jobs/admin/password_reset_job.rb"
23
+ end
24
+
20
25
  def copy_layout
21
26
  template "layouts/admin/auth.html.slim",
22
27
  "app/views/layouts/admin/auth.html.slim"
@@ -17,6 +17,11 @@ module Trek
17
17
  "app/controllers/admin/user_sessions_controller.rb"
18
18
  end
19
19
 
20
+ def copy_passkey_sessions_controller
21
+ template "controllers/admin/passkey_sessions_controller.rb",
22
+ "app/controllers/admin/passkey_sessions_controller.rb"
23
+ end
24
+
20
25
  def copy_layout
21
26
  template "layouts/admin/auth.html.slim",
22
27
  "app/views/layouts/admin/auth.html.slim"
@@ -13,9 +13,22 @@ module Trek
13
13
  source_root File.expand_path("../../templates", __dir__)
14
14
 
15
15
  def add_routes
16
+ # A separate route call, so apps installed before get it added rather than their users
17
+ # block duplicated
18
+ route <<~RUBY, namespace: :admin
19
+ resources :active_sessions, only: :destroy do
20
+ delete :others, action: :destroy_others, on: :collection
21
+ end
22
+ RUBY
23
+ route <<~RUBY, namespace: :admin
24
+ resources :passkeys, only: %i[create destroy] do
25
+ get :options, on: :collection
26
+ end
27
+ RUBY
16
28
  route <<~RUBY, namespace: :admin
17
29
  resources :users do
18
30
  patch :theme, on: :collection
31
+ post :require_password_renewal, on: :collection
19
32
  end
20
33
  RUBY
21
34
  end
@@ -29,6 +42,16 @@ module Trek
29
42
  directory "views/admin/users",
30
43
  "app/views/admin/users"
31
44
  end
45
+
46
+ def copy_active_sessions
47
+ template "controllers/admin/active_sessions_controller.rb",
48
+ "app/controllers/admin/active_sessions_controller.rb"
49
+ end
50
+
51
+ def copy_passkeys
52
+ template "controllers/admin/passkeys_controller.rb",
53
+ "app/controllers/admin/passkeys_controller.rb"
54
+ end
32
55
  end
33
56
  end
34
57
  end
@@ -0,0 +1,56 @@
1
+ require "rails/generators/actions"
2
+ require "rails/generators/migration"
3
+ require "generators/trek/helpers"
4
+
5
+ module Trek
6
+ module Generators
7
+ module Install
8
+ class AuditLogGenerator < Rails::Generators::Base
9
+ include Rails::Generators::Migration
10
+ include Trek::Generators::Helpers
11
+
12
+ desc "Create the AuditLog model, record the admin's requests, and list them in the admin."
13
+
14
+ source_root File.expand_path("../templates", __dir__)
15
+
16
+ def copy_model
17
+ template "models/audit_log.rb", "app/models/audit_log.rb"
18
+ end
19
+
20
+ def copy_migration_file
21
+ return unless defined?(ActiveRecord)
22
+
23
+ migration_template("migrations/audit_logs.rb.erb",
24
+ "db/migrate/trek_audit_logs.rb",
25
+ migration_class_name:)
26
+ end
27
+
28
+ def copy_initializer
29
+ template "initializers/audit_log.rb", "config/initializers/audit_log.rb"
30
+ end
31
+
32
+ def copy_controller
33
+ template "controllers/admin/audit_logs_controller.rb",
34
+ "app/controllers/admin/audit_logs_controller.rb"
35
+ end
36
+
37
+ def copy_views
38
+ directory "views/admin/audit_logs", "app/views/admin/audit_logs"
39
+ end
40
+
41
+ def copy_policy
42
+ template "policies/audit_log.rb", "app/policies/admin/audit_log_policy.rb"
43
+ end
44
+
45
+ def add_routes
46
+ route "resources :audit_logs, only: %i[index show]", namespace: :admin
47
+ end
48
+
49
+ def copy_specs
50
+ template "spec/models/audit_log_spec.rb", "spec/models/audit_log_spec.rb"
51
+ template "spec/requests/admin/audit_logs_spec.rb", "spec/requests/admin/audit_logs_spec.rb"
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -28,8 +28,8 @@ module Trek
28
28
  end
29
29
 
30
30
  def copy_user_session_model
31
- template "models/user_session.rb",
32
- "app/models/user_session.rb"
31
+ template "models/user/session.rb",
32
+ "app/models/user/session.rb"
33
33
  end
34
34
 
35
35
  def copy_user_sessions_migration
@@ -40,6 +40,64 @@ module Trek
40
40
  migration_class_name:)
41
41
  end
42
42
 
43
+ def copy_user_previous_password_model
44
+ template "models/user/previous_password.rb",
45
+ "app/models/user/previous_password.rb"
46
+ end
47
+
48
+ def copy_user_previous_passwords_migration
49
+ return unless defined?(ActiveRecord)
50
+
51
+ migration_template("migrations/user_previous_passwords.rb.erb",
52
+ "db/migrate/trek_user_previous_passwords.rb",
53
+ migration_class_name:)
54
+ end
55
+
56
+ def copy_user_session_tracking_migration
57
+ return unless defined?(ActiveRecord)
58
+
59
+ migration_template("migrations/user_session_tracking.rb.erb",
60
+ "db/migrate/trek_user_session_tracking.rb",
61
+ migration_class_name:)
62
+ end
63
+
64
+ def copy_user_sign_ins_migration
65
+ return unless defined?(ActiveRecord)
66
+
67
+ migration_template("migrations/user_sign_ins.rb.erb",
68
+ "db/migrate/trek_user_sign_ins.rb",
69
+ migration_class_name:)
70
+ end
71
+
72
+ def copy_user_password_renewal_migration
73
+ return unless defined?(ActiveRecord)
74
+
75
+ migration_template("migrations/user_password_renewal.rb.erb",
76
+ "db/migrate/trek_user_password_renewal.rb",
77
+ migration_class_name:)
78
+ end
79
+
80
+ def copy_user_sign_in_lockout_migration
81
+ return unless defined?(ActiveRecord)
82
+
83
+ migration_template("migrations/user_sign_in_lockout.rb.erb",
84
+ "db/migrate/trek_user_sign_in_lockout.rb",
85
+ migration_class_name:)
86
+ end
87
+
88
+ def copy_user_passkey_model
89
+ template "models/user/passkey.rb",
90
+ "app/models/user/passkey.rb"
91
+ end
92
+
93
+ def copy_user_passkeys_migration
94
+ return unless defined?(ActiveRecord)
95
+
96
+ migration_template("migrations/user_passkeys.rb.erb",
97
+ "db/migrate/trek_user_passkeys.rb",
98
+ migration_class_name:)
99
+ end
100
+
43
101
  def inject_authentication_into_user_model
44
102
  inject_into_class user_model_path,
45
103
  USER_MODEL_CLASS_NAME,
@@ -51,6 +109,11 @@ module Trek
51
109
  route "resources :user_password_resets, param: :token, only: %i[new create edit update]", namespace: :admin
52
110
  route 'get :login, to: "user_sessions#new", as: :login', namespace: :admin
53
111
  route 'delete :logout, to: "user_sessions#destroy", as: :logout', namespace: :admin
112
+ route <<~RUBY, namespace: :admin
113
+ resource :passkey_session, only: :create do
114
+ get :options
115
+ end
116
+ RUBY
54
117
  end
55
118
 
56
119
  def autocorrect_user_model
@@ -66,9 +129,20 @@ module Trek
66
129
  def user_model_injection
67
130
  <<-RUBY
68
131
  has_secure_password
132
+ # Refuses the last 5 passwords; set password_history_size to change it.
133
+ include Trek::Users::PasswordHistory
134
+ # Asks for a new password after an admin requires it, or after password_expires_after (none by
135
+ # default).
136
+ include Trek::Users::PasswordRenewal
137
+ # The previous sign-in, and an email on a sign-in from a new browser (new_sign_in_email).
138
+ include Trek::Users::SignIns
139
+ # Locks signing in for a while after 5 failed sign-ins in a row, longer on each further failure.
140
+ include Trek::Users::SignInLockout
141
+ # Passkeys to sign in with instead of the password (user.passkeys).
142
+ include Trek::Users::Passkeys
69
143
  # Token purposes: :password_reset (15 min) comes from has_secure_password;
70
144
  # :invite (1 week) comes from Trek::Users::Invitable.
71
- has_many :user_sessions, dependent: :destroy
145
+ has_many :sessions, dependent: :destroy
72
146
 
73
147
  scope :privileged, -> { where(role: Trek::Users::Roles::PRIVILEGED_ROLES) }
74
148
 
@@ -6,8 +6,7 @@ module Trek
6
6
  module Install
7
7
  class EsbuildGenerator < Rails::Generators::Base
8
8
  NPM_DEPENDENCIES = {
9
- esbuild: "^0.15.11",
10
- "esbuild-plugin-import-glob": "^0.1.1",
9
+ esbuild: "^0.28.0",
11
10
  "esbuild-rails": "^1.0.3",
12
11
  "esbuild-style-plugin": "^1.6.0"
13
12
  }
@@ -38,6 +38,24 @@ module Trek
38
38
  end
39
39
  end
40
40
 
41
+ def copy_views_admin_mailer_new_sign_in
42
+ extensions.each do |extension|
43
+ locales.each do |lang|
44
+ template "views/admin_mailer/new_sign_in.#{lang}#{extension}",
45
+ "app/views/admin_mailer/new_sign_in.#{lang}#{extension}"
46
+ end
47
+ end
48
+ end
49
+
50
+ def copy_views_admin_mailer_sign_in_locked
51
+ extensions.each do |extension|
52
+ locales.each do |lang|
53
+ template "views/admin_mailer/sign_in_locked.#{lang}#{extension}",
54
+ "app/views/admin_mailer/sign_in_locked.#{lang}#{extension}"
55
+ end
56
+ end
57
+ end
58
+
41
59
  def copy_views_passwords_mailer_reset
42
60
  extensions.each do |extension|
43
61
  locales.each do |lang|
@@ -45,17 +45,19 @@ module Trek
45
45
  after: %r{^require ["']rspec/rails["'].*\n}
46
46
  end
47
47
 
48
- # Every file under the templates' db/seeds, spec/models and spec/requests is installed, so a new
48
+ # Every file under the templates' db/seeds and spec directories is installed, so a new
49
49
  # template needs no change here.
50
50
  def copy_seeds
51
51
  directory "db/seeds", "db/seeds"
52
52
  end
53
53
 
54
+ # The audit log's specs come with it, from `trek:install:audit_log`, and the tags' (model and policies) from
55
+ # `trek:taxonomies`.
54
56
  def copy_specs
55
- directory "spec/models", "spec/models"
56
- directory "spec/requests", "spec/requests"
57
+ directory "spec/models", "spec/models", exclude_pattern: /audit_log|tag_spec/
58
+ directory "spec/requests", "spec/requests", exclude_pattern: /audit_log/
59
+ directory "spec/mailers", "spec/mailers"
57
60
  directory "spec/fixtures", "spec/fixtures"
58
- # The tag policies' specs come with them, from `trek:taxonomies`.
59
61
  directory "spec/policies", "spec/policies", exclude_pattern: /tag(_category)?_policy_spec/
60
62
  end
61
63
  end
@@ -7,14 +7,11 @@ module Trek
7
7
  class PostcssGenerator < Rails::Generators::Base
8
8
  NPM_DEPENDENCIES = {
9
9
  postcss: "^8.4.18",
10
- "postcss-cli": "^10.0.0",
11
10
  "postcss-flexbugs-fixes": "^5.0.2",
12
11
  "postcss-import": "^15.0.0",
13
12
  "postcss-inline-svg": "^5.0.0",
14
13
  "postcss-modules": "^5.0.0",
15
- "postcss-nesting": "^10.2.0",
16
- "postcss-preset-env": "^7.8.2",
17
- "postcss-url": "^10.1.3"
14
+ "postcss-preset-env": "^7.8.2"
18
15
  }
19
16
 
20
17
  include Trek::Generators::Helpers
@@ -0,0 +1,49 @@
1
+ require "rails/generators/actions"
2
+ require "generators/trek/helpers"
3
+
4
+ module Trek
5
+ module Generators
6
+ module Install
7
+ class SecurityHeadersGenerator < Rails::Generators::Base
8
+ include Trek::Generators::Helpers
9
+
10
+ desc "Set up the Content Security Policy (report-only at first) and the Permissions-Policy header."
11
+
12
+ source_root File.expand_path("../templates", __dir__)
13
+
14
+ def copy_content_security_policy
15
+ copy_initializer "content_security_policy.rb"
16
+ end
17
+
18
+ def copy_permissions_policy
19
+ copy_initializer "permissions_policy.rb"
20
+ end
21
+
22
+ # Where browsers send the policy's violations (Trek::CspReportsController)
23
+ def add_reports_route
24
+ route 'post "csp-reports", to: "trek/csp_reports#create", as: :csp_reports'
25
+ end
26
+
27
+ private
28
+
29
+ # Replaces Rails' commented-out example, but not a policy the app wrote: that one is the app's
30
+ # to reconcile with Trek's.
31
+ def copy_initializer(name)
32
+ path = "config/initializers/#{name}"
33
+ existing = File.join(destination_root, path)
34
+
35
+ if File.exist?(existing) && !commented_out?(existing) && File.read(existing) != File.read(find_in_source_paths("initializers/#{name}"))
36
+ say_status :skip, "#{path} has the app's own policy: compare it with Trek's (docs: Security headers)", :yellow
37
+ return
38
+ end
39
+
40
+ template "initializers/#{name}", path, force: true
41
+ end
42
+
43
+ def commented_out?(path)
44
+ File.readlines(path).all? { |line| line.strip.empty? || line.strip.start_with?("#") }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -5,7 +5,7 @@ module Trek
5
5
  module Generators
6
6
  module Install
7
7
  class YarnGenerator < Rails::Generators::Base
8
- YARN_VERSION = "4.18.0"
8
+ YARN_VERSION = "4.18.1"
9
9
 
10
10
  include Trek::Generators::Helpers
11
11