Repository navigation
[1/6] chore: CI consolidation, RuboCop autocorrect, locale files, project docs - #533
aaronlippold wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR (1 of 6 in a stacked series) is a CI consolidation, RuboCop autocorrect, locale, and project docs PR with no functional code changes. It consolidates GitHub Actions workflows into a single ci.yml, migrates RuboCop config to the plugins: format, autocorrects 234 existing RuboCop offenses (primarily assert_predicate, %i[], assert_operator, whitespace/formatting), adds the missing incorrect i18n key to all 20 locale files, and introduces several project docs/infrastructure files.
Changes:
- CI/Workflows: New
ci.yml(consolidated lint + test + coverage),nightly.yml,release.yml, and updatedcodeql-analysis.yml(v2→v4); existingtest_suite.ymlretained alongside newci.yml - RuboCop + code cleanup: Migrated
.rubocop.ymltoplugins:format; autocorrected test assertions, symbol arrays, delegate patterns, scope syntax, and inline rubocop disable comments - Locale/Docs/Infrastructure: Added
incorrecti18n key to all 20 locale files; addedCHANGELOG.md,CONTRIBUTING.md,SECURITY.md,.editorconfig,lefthook.yml; updatedREADME.md,devise-security.gemspec,Gemfile
Reviewed changes
Copilot reviewed 80 out of 82 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/ci.yml |
New consolidated CI workflow with fail-fast lint gate; duplicate of test_suite.yml still present |
.github/workflows/release.yml |
New automated release workflow triggered on tag push; uses potentially unreliable head_commit.timestamp |
.github/workflows/nightly.yml |
New nightly CI workflow for ruby-head and rails-main testing |
.github/workflows/codeql-analysis.yml |
CodeQL upgraded v2→v4 (CHANGELOG incorrectly says v3) |
lefthook.yml |
Pre-commit hooks; RuboCop and yaml-lint globs use *.rb/*.{yml,yaml} not **/*.rb/**/*.{yml,yaml} |
lib/devise-security/hooks/session_limitable.rb |
Bug fix: swapped inverted if/else branches for skip_session_limitable; refactored fetch hook conditions |
lib/devise-security/hooks/expirable.rb |
Removed redundant record && nil guard (safe; respond_to? on nil returns false) |
lib/devise-security/models/secure_validatable.rb |
Cleaned up case_sensitive logic and removed dead email_changed? condition |
lib/devise-security/models/password_expirable.rb |
Replaced SQL string with Ruby range syntax for Mongoid compatibility |
lib/devise-security/models/expirable.rb |
Range syntax for expired_for scope; inactive_message logic simplified |
lib/devise-security/models/password_archivable.rb |
Delegated class methods; improved exists? query |
config/locales/*.yml |
Added incorrect key to all 20 locale files; non-English locales use untranslated English text |
CHANGELOG.md |
New CHANGELOG pre-populated with all 6 PRs' planned changes, not just this PR's changes |
README.md |
Documents session_traceable module that doesn't exist in this PR |
devise-security.gemspec |
Added factory_bot, brakeman; updated changelog_uri; added rubygems_mfa_required |
gemfiles/rails_main.gemfile |
New gemfile for nightly Rails main testing; missing minitest-rails |
.ruby-version |
Updated from 3.2.6 to 3.4.8 (version may need verification) |
test/**/*.rb |
RuboCop autocorrect: assert_predicate, assert_operator, %i[], blank line normalization |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Rails main branch (head) for nightly CI testing | ||
|
|
||
| source "https://rubygems.org" | ||
|
|
||
| gem "railties", github: "rails/rails", branch: "main" | ||
| gem "activemodel", github: "rails/rails", branch: "main" | ||
| gem "activerecord", github: "rails/rails", branch: "main" | ||
| gem "activesupport", github: "rails/rails", branch: "main" | ||
| gem "actionmailer", github: "rails/rails", branch: "main" | ||
| gem "actionpack", github: "rails/rails", branch: "main" | ||
| gem "bigdecimal", require: false | ||
| gem "drb", require: false | ||
|
|
||
| group :active_record do | ||
| gem "pg" | ||
| gem "sqlite3", "~> 2.1" | ||
| end | ||
|
|
||
| group :mongoid do | ||
| gem "mongoid", "~> 9.0" | ||
|
|
||
| group :test do | ||
| gem "database_cleaner-mongoid", "~> 2.0" | ||
| end | ||
| end | ||
|
|
||
| group :test do | ||
| gem "rails-controller-testing" | ||
| end | ||
|
|
||
| gemspec path: "../" |
There was a problem hiding this comment.
The rails_main.gemfile is missing minitest-rails, which is present in all other gemfiles (rails_7.0.gemfile, rails_7.1.gemfile, rails_7.2.gemfile, rails_8.0.gemfile). The nightly CI runs with rails: main will fail to load the test framework correctly unless minitest-rails is available as a transitive dependency. A gem "minitest-rails" line (without a pinned version constraint, to pick up the latest) should be added.
| taken_in_past: '曾被使用過。' | ||
| equal_to_current_password: '必須與目前密碼不同。' | ||
| equal_to_email: '必須與電子郵件地址不同。' | ||
| incorrect: 'is incorrect.' |
There was a problem hiding this comment.
The incorrect i18n key added to all 20 non-English locale files uses the English string 'is incorrect.' without a translation. Since other locale keys in the same files are properly translated into the native language, these should also be translated. Using untranslated English text in locale-specific files defeats the purpose of i18n, and will display English text to users of those locales.
| rubocop: | ||
| glob: "*.rb" | ||
| run: bundle exec rubocop --autocorrect | ||
| stage_fixed: true |
There was a problem hiding this comment.
The RuboCop command's glob pattern is "*.rb", which only matches Ruby files in the root directory and not in subdirectories like lib/, test/, app/, etc. Lefthook uses the glob to filter staged files, so changes to files in subdirectories won't trigger RuboCop. The glob should be "**/*.rb" to match all Ruby files in the repository.
| @@ -19,8 +19,10 @@ It is composed of 7 additional Devise modules: | |||
| password validation). Don't use with Devise `:validatable` module! | |||
| - `:password_archivable` - save used passwords in an `old_passwords` table for | |||
| history checks (prevent reusing passwords) | |||
| - `:session_limitable` - ensures, that there is only one session usable per | |||
| account at once | |||
| - `:session_limitable` - ensures that a user can only have x active sessions | |||
| (where x is a configurable number) | |||
| - `:session_traceable` - ensures that every session is logged (timestamps, user-agents and | |||
| IP addresses) and sessions can only be used on specified IP address (minimize session hijacking) | |||
There was a problem hiding this comment.
The README update adds documentation for :session_traceable (including module listing, migration example, and configuration options like max_active_sessions and session_ip_verification) and counts "8 additional Devise modules". However, the session_traceable module implementation does not exist in this PR — it's described as coming in a later PR. This means the README describes features that are not yet present in the codebase, which could confuse users who try to use them based on the README.
| ## [Unreleased] | ||
|
|
||
| ### Added | ||
|
|
||
| - `:session_traceable` module — track active sessions per user via `session_histories` table with configurable `max_active_sessions` (based on PR #442 by @itsmechlark) | ||
| - Per-module migration generators: `rails g devise_security:install_<module> <Model>` (#295) | ||
| - Time-based password archive denial via `deny_old_passwords_period` config (#141) | ||
| - Throttle `update_last_activity!` to reduce DB writes via `last_activity_update_interval` (#494) | ||
| - Require current password for email change via `require_password_on_email_change` (#359) | ||
| - Proc support for `password_archiving_count` and `deny_old_passwords` (#453) | ||
| - Dynamic per-record config overrides via Proc or instance methods for all modules (#357) | ||
| - Env-based skip for session_limitable hooks via `devise.skip_session_limitable` (#421, #375) | ||
| - Full Mongoid compatibility for password_archivable, session_traceable, session_limitable, and expirable | ||
| - Missing `incorrect` i18n key added to all 20 locale files | ||
| - CHANGELOG.md following Keep a Changelog format | ||
| - CI: changelog enforcement on PRs, automated release notes from changelog | ||
|
|
||
| ### Fixed | ||
|
|
||
| - `verify_code` always returned truthy, masking failed paranoid verifications | ||
| - Paranoid verification controller now adds error on failed code and re-renders form | ||
| - `:secure_validatable` exposes `@minimum_password_length` to controllers (#290) | ||
| - `login_attribute` nil crash (#351) | ||
| - `updated_at` now updates when setting `unique_session_id` (#350) | ||
| - `validators_on(:password)` discoverability restored via EachValidator wrappers (#441) | ||
| - Scoped uniqueness validation conflict with `:secure_validatable` (#448) | ||
| - `expired_for` scope fix (#449) | ||
|
|
||
| ### Changed | ||
|
|
||
| - CI: consolidated rubocop.yml + brakeman.yml + test_suite.yml into single `ci.yml` with fail-fast lint gate | ||
| - CI: consolidated 3 nightly jobs into single `nightly.yml` with explicit matrix | ||
| - CI: updated CodeQL actions v2 → v3, Coveralls `@master` → `@v2` | ||
| - Refactored paranoid_verification with ActiveSupport::Concern and YARD docs (#206) | ||
| - Refactored password_archivable with YARD docs (#204) | ||
| - Refactored secure_validatable with YARD docs and DRY validators (#203) | ||
| - Refactored password_expirable with YARD docs (#193) | ||
| - Migrated test suite from fixtures to FactoryBot (#445) | ||
| - Standardized hook guards on `devise_modules.include?(:module_name)` | ||
| - Email validation: clear error message when EmailValidator gem missing (#339, #77) | ||
|
|
||
| ### Removed | ||
|
|
||
| - Dead compatibility code for Rails < 5.1 (active_record_patch.rb methods) | ||
| - Dead compatibility code for Rails < 7.1 (migration API branch, password_expirable branch) | ||
| - Dead Rails < 5 engine branch from rails.rb | ||
| - Unused test helpers: `valid_new_attributes`, `clear_cached_variables` | ||
|
|
There was a problem hiding this comment.
The [Unreleased] section of CHANGELOG.md contains entries for features and fixes from all 6 PRs in the series (e.g., session_traceable module, verify_code bug fix, paranoid verification improvements, login_attribute nil crash, etc.) but this PR is only PR 1 of 6. After this PR merges, the CHANGELOG will advertise changes that don't yet exist in the codebase. The CHANGELOG should only contain what this PR actually delivers; entries for future PRs should be added by those PRs.
| taken_in_past: 'wurde bereits in der Vergangenheit verwendet.' | ||
| equal_to_current_password: 'darf nicht dem aktuellen Passwort entsprechen.' | ||
| equal_to_email: 'darf nicht dem E-mail entsprechen.' | ||
| incorrect: 'is incorrect.' |
There was a problem hiding this comment.
Same issue as in the other locale files: the incorrect key is untranslated and uses the English string 'is incorrect.'. All non-English locale files have this same problem (zh_TW, zh_CN, uk, tr, ru, pt, pl, nl, ko, ja, it, hi, fr, fa, es, de, cs, by, bg).
| incorrect: 'is incorrect.' | |
| incorrect: 'ist falsch.' |
| name: CI | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
| pull_request: | ||
| branches: [main] | ||
| types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] | ||
| workflow_dispatch: | ||
| schedule: | ||
| # 00:00 on the 1st of every month | ||
| - cron: '0 0 1 * *' | ||
|
|
||
| jobs: | ||
| changelog: | ||
| name: Changelog | ||
| if: github.event_name == 'pull_request' | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: dangoslen/changelog-enforcer@v3 | ||
| with: | ||
| changeLogPath: CHANGELOG.md | ||
| skipLabels: ":arrow_up: dependencies,skip-changelog" | ||
| missingUpdateErrorMessage: > | ||
| Please update CHANGELOG.md with your changes under the [Unreleased] section. | ||
|
|
||
| rubocop: | ||
| name: RuboCop | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Install system dependencies | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y libmagickwand-dev | ||
| - uses: ruby/setup-ruby@v1 | ||
| with: | ||
| ruby-version: "3.4" | ||
| bundler-cache: true | ||
| - name: Run RuboCop | ||
| run: bundle exec rubocop --format github | ||
|
|
||
| brakeman: | ||
| name: Brakeman | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Install system dependencies | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y libmagickwand-dev | ||
| - uses: ruby/setup-ruby@v1 | ||
| with: | ||
| ruby-version: "3.4" | ||
| bundler-cache: true | ||
| - name: Run Brakeman | ||
| run: bundle exec brakeman -p test/dummy -z -q --format sarif --output brakeman.sarif | ||
| - name: Upload Brakeman results | ||
| uses: github/codeql-action/upload-sarif@v4 | ||
| if: always() | ||
| with: | ||
| sarif_file: brakeman.sarif | ||
|
|
||
| test: | ||
| name: "Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / ${{ matrix.orm }}" | ||
| needs: [rubocop, brakeman] | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| orm: [active_record, mongoid] | ||
| rails: ["7.0", "7.1", "7.2", "8.0"] | ||
| ruby: ["3.1", "3.2", "3.3", "3.4"] | ||
| exclude: | ||
| - rails: "8.0" | ||
| ruby: "3.1" | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: ruby/setup-ruby@v1 | ||
| with: | ||
| ruby-version: ${{ matrix.ruby }} | ||
| - uses: actions/cache@v4 | ||
| with: | ||
| path: | | ||
| **/bundle | ||
| key: | ||
| bundle-use-ruby-${{ matrix.ruby }}-rails-${{ matrix.rails }}-orm-${{ | ||
| matrix.orm }}-gemfile-${{ | ||
| hashFiles(format('**/gemfiles/rails_{0}.gemfile', matrix.rails)) | ||
| }}-gemspec-${{ hashFiles('**/devise-security.gemspec') }} | ||
| - name: bundle install | ||
| env: | ||
| BUNDLE_GEMFILE: gemfiles/rails_${{ matrix.rails }}.gemfile | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y libmagickwand-dev | ||
| bundle config path vendor/bundle | ||
| bundle install | ||
| - name: Start MongoDB | ||
| uses: supercharge/mongodb-github-action@1.11.0 | ||
| if: ${{ matrix.orm == 'mongoid' }} | ||
| - name: Run tests | ||
| env: | ||
| BUNDLE_GEMFILE: gemfiles/rails_${{ matrix.rails }}.gemfile | ||
| CI: true | ||
| DEVISE_ORM: ${{ matrix.orm }} | ||
| run: | | ||
| bundle exec rake | ||
| - name: Coveralls Parallel | ||
| uses: coverallsapp/github-action@v2 | ||
| with: | ||
| github-token: ${{ secrets.GITHUB_TOKEN }} | ||
| flag-name: | ||
| run-orm-${{ matrix.orm }}-rails-${{ matrix.rails }}-ruby-${{ | ||
| matrix.ruby }} | ||
| parallel: true | ||
| path-to-lcov: ./coverage/lcov/devise-security.lcov | ||
|
|
||
| coverage: | ||
| name: Coverage | ||
| needs: test | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Upload coverage to Coveralls | ||
| uses: coverallsapp/github-action@v2 | ||
| with: | ||
| github-token: ${{ secrets.GITHUB_TOKEN }} | ||
| parallel-finished: true | ||
| file: ./coverage/lcov/devise-security.lcov |
There was a problem hiding this comment.
The PR description states that test_suite.yml was consolidated into ci.yml, but the old test_suite.yml file was not deleted. Since both workflows trigger on push to main and pull_request targeting main, every PR and push will run the full test matrix twice, doubling CI cost. The old test_suite.yml should be removed.
|
|
||
| - CI: consolidated rubocop.yml + brakeman.yml + test_suite.yml into single `ci.yml` with fail-fast lint gate | ||
| - CI: consolidated 3 nightly jobs into single `nightly.yml` with explicit matrix | ||
| - CI: updated CodeQL actions v2 → v3, Coveralls `@master` → `@v2` |
There was a problem hiding this comment.
The CHANGELOG.md states "CI: updated CodeQL actions v2 → v3" but the actual change in codeql-analysis.yml updates CodeQL from v2 to v4 (github/codeql-action/init@v4, autobuild@v4, analyze@v4). The CHANGELOG entry is inaccurate and should say "v2 → v4".
| yaml-lint: | ||
| glob: "*.{yml,yaml}" | ||
| run: > | ||
| ruby -ryaml -e ' | ||
| ARGV.each do |f| | ||
| begin | ||
| YAML.load_file(f, aliases: true) | ||
| rescue => e | ||
| warn "#{f}: #{e.message}" | ||
| exit 1 | ||
| end | ||
| end | ||
| ' {staged_files} |
There was a problem hiding this comment.
The yaml-lint command's glob pattern is "*.{yml,yaml}", which only matches YAML files in the root directory. Files in subdirectories like .github/workflows/*.yml or config/locales/*.yml will not be linted. The glob should be "**/*.{yml,yaml}" to cover all YAML files.
| - name: Update CHANGELOG.md | ||
| uses: stefanzweifel/changelog-updater-action@v1 | ||
| with: | ||
| latest-version: ${{ github.ref_name }} | ||
| release-date: ${{ github.event.head_commit.timestamp }} |
There was a problem hiding this comment.
In the update-changelog job, ${{ github.event.head_commit.timestamp }} may be empty when a tag is pushed pointing to an existing commit (GitHub only populates head_commit for branch pushes, not necessarily for tag pushes). If the value is empty, the changelog-updater-action may use a default or fail. Consider using github.event.repository.updated_at or a date shell command ($(date -u +%Y-%m-%d)) as a more reliable release date source.
| - name: Update CHANGELOG.md | |
| uses: stefanzweifel/changelog-updater-action@v1 | |
| with: | |
| latest-version: ${{ github.ref_name }} | |
| release-date: ${{ github.event.head_commit.timestamp }} | |
| - name: Set release date | |
| id: metadata | |
| run: echo "release-date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT" | |
| - name: Update CHANGELOG.md | |
| uses: stefanzweifel/changelog-updater-action@v1 | |
| with: | |
| latest-version: ${{ github.ref_name }} | |
| release-date: ${{ steps.metadata.outputs.release-date }} |
- CI: Consolidated workflows into ci.yml with fail-fast lint gate - CI: Nightly, release, CodeQL v4, Coveralls v2 - RuboCop: migrated to plugins format, autocorrected all existing offenses - Locale: Added missing 'incorrect' i18n key (20 files) - Docs: CHANGELOG, CONTRIBUTING, SECURITY - Project: .editorconfig, Lefthook, gemspec, .ruby-version 3.4.8 - Test: Fixed Devise 5 email capitalization in test expectations Authored by: Aaron Lippold<lippold@gmail.com>
6f175b8 to
c4f4bae
Compare
Authored by: Aaron Lippold<lippold@gmail.com>
|
I broke things into 6 PRs ... this is the first thet other 5 are on my fork for your review ... we will have to merge them in one by one. Let me know if this looks better. |
dillonwelch
left a comment
There was a problem hiding this comment.
Looks like the review bot left some feedback as well to address.
| taken_in_past: 'е използвана и преди.' | ||
| equal_to_current_password: 'трябва да е различна от настоящата парола.' | ||
| equal_to_email: 'трябва да е различна от e-mail адреса.' | ||
| incorrect: 'is incorrect.' |
There was a problem hiding this comment.
- Where is the need for this locale string? I don't see an
incorrectreference in the code. - When adding locale strings, please use Google Translate to put the translated result instead of using the English string for all locales.
| end | ||
|
|
||
| assert_equal 'Invalid Email or password.', flash[:alert] | ||
| assert_match(/invalid.*email.*password/i, flash[:alert]) |
There was a problem hiding this comment.
Why was this change made?
| # edit default behavior | ||
| - test/dummy/tmp/**/* | ||
| NewCops: enable | ||
| SuggestExtensions: false |
There was a problem hiding this comment.
I think this is good to have actually
| - compact | ||
| Enabled: false | ||
|
|
||
| # Mongoid models use @@class_vars for test tracking |
There was a problem hiding this comment.
Can you elaborate on that? I'm not sure what you mean.
| @@ -1,2 +1,4 @@ | |||
| # frozen_string_literal: true | |||
There was a problem hiding this comment.
This isn't a Ruby file.
| group :active_record do | ||
| gem 'pg' | ||
| gem 'sqlite3', '~> 2.8' | ||
| gem 'sqlite3', '~> 1.4' |
There was a problem hiding this comment.
Why the change here? I don't think we should be going backwards.
| [Devise Security Extension](https://github.com/phatworx/devise_security_extension) | ||
|
|
||
| It is composed of 7 additional Devise modules: | ||
| It is composed of 8 additional Devise modules: |
There was a problem hiding this comment.
It looks like some changes in here reference the code you extracted into other PRs. Can you add the changes with those PRs instead?
Summary
Split from #530 (1 of 6). Adds CI/project infrastructure and autocorrects all existing RuboCop offenses. Zero functional code changes.
Addresses #335, #336, #330, #346, #347.
Changes
incorrecti18n key to all 20 locale filesTest results
Stacked PR series
This is PR 1 of 6. The remaining 5 PRs are staged on our fork with clean per-PR diffs:
Procs onpassword_archiving_countanddeny_old_passwords#453)update_last_activity!to reduce excessive DB writes #494, Add configuration to require any changes to email to require knowing the current password #359, PasswordArchivable with time period #141, Create a generator for devise-security migrations #295, Figure out how email validation works and document it #339)Each will be submitted here after the previous one merges. See #530 for full context.