trek 2.3.1 → 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.
- checksums.yaml +4 -4
- data/.node-version +1 -1
- data/.yarn/releases/yarn-4.18.1.cjs +1000 -0
- data/.yarnrc.yml +3 -5
- data/AGENTS.md +7 -4
- data/CHANGELOG.md +55 -0
- data/Gemfile.lock +35 -1
- data/Makefile +12 -0
- data/app/assets/icons/trek/passkey.svg +5 -0
- data/app/assets/stylesheets/trek/base.css +6 -0
- data/app/components/trek/entries/item_component/item_component.css +6 -0
- data/app/components/trek/form/image_field_component/image_field_component.html.slim +9 -0
- data/app/components/trek/form/password_strength_component/password_strength_component.css +56 -0
- data/app/components/trek/form/password_strength_component/password_strength_component.html.slim +8 -0
- data/app/components/trek/form/password_strength_component/password_strength_component.js +74 -0
- data/app/components/trek/form/password_strength_component.rb +29 -0
- data/app/components/trek/gate_component/gate_component.css +7 -0
- data/app/components/trek/icon/sprite_component.rb +3 -2
- data/app/components/trek/section_component/section_component.css +22 -0
- data/app/components/trek/section_component/section_component.html.slim +5 -0
- data/app/components/trek/section_component/section_component.js +1 -0
- data/app/components/trek/section_component.rb +14 -0
- data/app/controllers/concerns/trek/auditable.rb +103 -0
- data/app/controllers/concerns/trek/filters.rb +2 -0
- data/app/controllers/concerns/trek/passkeys.rb +37 -0
- data/app/controllers/concerns/trek/scopes.rb +9 -2
- data/app/controllers/trek/csp_reports_controller.rb +62 -0
- data/app/controllers/trek/resource_controller.rb +7 -1
- data/app/helpers/trek/audit_logs_helper.rb +21 -0
- data/app/helpers/trek/user_agents_helper.rb +36 -0
- data/app/javascript/controllers/index.js +3 -0
- data/app/javascript/controllers/passkey_controller.js +118 -0
- data/app/javascript/trek.js +4 -0
- data/app/jobs/trek/audit_log/purge_job.rb +18 -0
- data/app/jobs/trek/purge_expired_sessions_job.rb +14 -0
- data/app/jobs/trek/resolve_session_hostname_job.rb +17 -0
- data/app/models/concerns/trek/audit_log.rb +30 -0
- data/app/models/concerns/trek/users/passkeys.rb +75 -0
- data/app/models/concerns/trek/users/password_history.rb +51 -0
- data/app/models/concerns/trek/users/password_policy.rb +85 -0
- data/app/models/concerns/trek/users/password_renewal.rb +51 -0
- data/app/models/concerns/trek/users/session_lifetime.rb +39 -0
- data/app/models/concerns/trek/users/sign_in_lockout.rb +67 -0
- data/app/models/concerns/trek/users/sign_ins.rb +31 -0
- data/app/models/concerns/trek/users/validations.rb +3 -2
- data/app/models/trek/feature.rb +6 -0
- data/app/models/trek/pwned_passwords.rb +40 -0
- data/app/models/trek/user_session.rb +10 -0
- data/app/policies/trek/audit_log_policy.rb +34 -0
- data/app/policies/trek/user_policy.rb +5 -0
- data/app/validators/not_disposable_validator.rb +18 -0
- data/config/brakeman.ignore +22 -0
- data/config/i18n-tasks.yml +7 -0
- data/config/locales/trek.en.yml +144 -39
- data/config/locales/trek.fr.yml +145 -40
- data/docs/.vitepress/config.mjs +8 -3
- data/docs/deploy.md +2 -0
- data/docs/proxies.md +53 -0
- data/docs/reference/components.md +1 -0
- data/docs/reference/controllers.md +1 -1
- data/docs/reference/environment-variables.md +2 -0
- data/docs/reference/generators/admin-user.md +1 -1
- data/docs/reference/generators/audit-log.md +113 -0
- data/docs/reference/generators/install.md +12 -3
- data/docs/reference/generators/security-headers.md +109 -0
- data/docs/reference/index.md +3 -0
- data/docs/reference/models/tag.md +4 -2
- data/docs/reference/models/user.md +123 -3
- data/docs/reference/validators.md +94 -0
- data/docs/security.md +90 -0
- data/lefthook.yml +8 -0
- data/lib/generators/trek/install/admin/user_password_resets_generator.rb +5 -0
- data/lib/generators/trek/install/admin/user_sessions_generator.rb +5 -0
- data/lib/generators/trek/install/admin/users_generator.rb +23 -0
- data/lib/generators/trek/install/audit_log_generator.rb +56 -0
- data/lib/generators/trek/install/authentication_generator.rb +77 -3
- data/lib/generators/trek/install/esbuild_generator.rb +1 -2
- data/lib/generators/trek/install/mailer_generator.rb +18 -0
- data/lib/generators/trek/install/model_specs_generator.rb +6 -4
- data/lib/generators/trek/install/postcss_generator.rb +1 -4
- data/lib/generators/trek/install/security_headers_generator.rb +49 -0
- data/lib/generators/trek/install/yarn_generator.rb +1 -1
- data/lib/generators/trek/install_generator.rb +62 -54
- data/lib/generators/trek/taxonomies_generator.rb +5 -0
- data/lib/generators/trek/templates/.gitlab-ci.yml +18 -2
- data/lib/generators/trek/templates/controllers/admin/active_sessions_controller.rb +21 -0
- data/lib/generators/trek/templates/controllers/admin/audit_logs_controller.rb +30 -0
- data/lib/generators/trek/templates/controllers/admin/passkey_sessions_controller.rb +41 -0
- data/lib/generators/trek/templates/controllers/admin/passkeys_controller.rb +37 -0
- data/lib/generators/trek/templates/controllers/admin/user_password_resets_controller.rb +4 -4
- data/lib/generators/trek/templates/controllers/admin/user_sessions_controller.rb +53 -4
- data/lib/generators/trek/templates/controllers/admin/users_controller.rb +20 -0
- data/lib/generators/trek/templates/controllers/concerns/trek/authentication.rb +58 -7
- data/lib/generators/trek/templates/helpers/application_helper.rb +0 -4
- data/lib/generators/trek/templates/initializers/audit_log.rb +11 -0
- data/lib/generators/trek/templates/initializers/content_security_policy.rb +49 -0
- data/lib/generators/trek/templates/initializers/permissions_policy.rb +19 -0
- data/lib/generators/trek/templates/jobs/admin/password_reset_job.rb +9 -0
- data/lib/generators/trek/templates/layouts/admin/auth.html.slim +0 -5
- data/lib/generators/trek/templates/layouts/admin.html.slim +0 -3
- data/lib/generators/trek/templates/mailers/admin/passwords_mailer.rb +1 -1
- data/lib/generators/trek/templates/mailers/admin_mailer.rb +28 -1
- data/lib/generators/trek/templates/migrations/audit_logs.rb.erb +27 -0
- data/lib/generators/trek/templates/migrations/tags.rb.erb +6 -1
- data/lib/generators/trek/templates/migrations/user_passkeys.rb.erb +24 -0
- data/lib/generators/trek/templates/migrations/user_password_renewal.rb.erb +8 -0
- data/lib/generators/trek/templates/migrations/user_previous_passwords.rb.erb +10 -0
- data/lib/generators/trek/templates/migrations/user_session_tracking.rb.erb +11 -0
- data/lib/generators/trek/templates/migrations/user_sign_in_lockout.rb.erb +7 -0
- data/lib/generators/trek/templates/migrations/user_sign_ins.rb.erb +13 -0
- data/lib/generators/trek/templates/models/audit_log.rb +3 -0
- data/lib/generators/trek/templates/models/tag_category.rb +2 -1
- data/lib/generators/trek/templates/models/user/passkey.rb +17 -0
- data/lib/generators/trek/templates/models/user/previous_password.rb +4 -0
- data/lib/generators/trek/templates/models/user/session.rb +8 -0
- data/lib/generators/trek/templates/policies/audit_log.rb +4 -0
- data/lib/generators/trek/templates/spec/mailers/admin/passwords_mailer_spec.rb +16 -0
- data/lib/generators/trek/templates/spec/mailers/admin_mailer_spec.rb +50 -0
- data/lib/generators/trek/templates/spec/models/audit_log_spec.rb +43 -0
- data/lib/generators/trek/templates/spec/models/tag_spec.rb +52 -0
- data/lib/generators/trek/templates/spec/models/user/session_spec.rb +68 -0
- data/lib/generators/trek/templates/spec/models/user_spec.rb +142 -4
- data/lib/generators/trek/templates/spec/requests/admin/active_sessions_spec.rb +119 -0
- data/lib/generators/trek/templates/spec/requests/admin/audit_logs_spec.rb +149 -0
- data/lib/generators/trek/templates/spec/requests/admin/passkey_sessions_spec.rb +103 -0
- data/lib/generators/trek/templates/spec/requests/admin/passkeys_spec.rb +104 -0
- data/lib/generators/trek/templates/spec/requests/admin/password_renewal_spec.rb +83 -0
- data/lib/generators/trek/templates/spec/requests/admin/sign_in_lockout_spec.rb +95 -0
- data/lib/generators/trek/templates/spec/requests/admin/sign_in_notices_spec.rb +93 -0
- data/lib/generators/trek/templates/spec/requests/admin/user_password_resets_spec.rb +55 -6
- data/lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb +31 -2
- data/lib/generators/trek/templates/views/admin/audit_logs/index.html.slim +35 -0
- data/lib/generators/trek/templates/views/admin/audit_logs/show.html.slim +50 -0
- data/lib/generators/trek/templates/views/admin/user_password_resets/edit.html.slim +17 -7
- data/lib/generators/trek/templates/views/admin/user_password_resets/new.html.slim +3 -2
- data/lib/generators/trek/templates/views/admin/user_sessions/new.html.slim +28 -2
- data/lib/generators/trek/templates/views/admin/users/_passkeys.html.slim +67 -0
- data/lib/generators/trek/templates/views/admin/users/_properties.html.slim +21 -0
- data/lib/generators/trek/templates/views/admin/users/_sessions.html.slim +70 -0
- data/lib/generators/trek/templates/views/admin/users/index.html.slim +13 -0
- data/lib/generators/trek/templates/views/admin/users/show.html.slim +10 -21
- data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.en.html.slim +22 -0
- data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.en.text.erb +16 -0
- data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.fr.html.slim +22 -0
- data/lib/generators/trek/templates/views/admin_mailer/new_sign_in.fr.text.erb +16 -0
- data/lib/generators/trek/templates/views/admin_mailer/sign_in_locked.en.html.slim +17 -0
- data/lib/generators/trek/templates/views/admin_mailer/sign_in_locked.en.text.erb +10 -0
- data/lib/generators/trek/templates/views/admin_mailer/sign_in_locked.fr.html.slim +18 -0
- data/lib/generators/trek/templates/views/admin_mailer/sign_in_locked.fr.text.erb +10 -0
- data/lib/trek/admin_ip_allowlist.rb +52 -0
- data/lib/trek/csp_reports.rb +99 -0
- data/lib/trek/data/common_passwords.txt +857 -0
- data/lib/trek/data/passkey_providers.json +404 -0
- data/lib/trek/disposable_email_domains.rb +53 -0
- data/lib/trek/engine.rb +12 -0
- data/lib/trek/passkey_providers.rb +19 -0
- data/lib/trek/reverse_dns.rb +27 -0
- data/lib/trek/version.rb +1 -1
- data/lib/trek.rb +13 -0
- data/package.json +3 -5
- data/renovate.json +11 -1
- data/trek.gemspec +3 -0
- data/yarn.lock +1757 -2019
- metadata +132 -4
- data/.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs +0 -541
- data/.yarn/releases/yarn-3.8.0.cjs +0 -875
- 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/
|
|
32
|
-
"app/models/
|
|
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 :
|
|
145
|
+
has_many :sessions, dependent: :destroy
|
|
72
146
|
|
|
73
147
|
scope :privileged, -> { where(role: Trek::Users::Roles::PRIVILEGED_ROLES) }
|
|
74
148
|
|
|
@@ -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
|
|
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-
|
|
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
|