{"id":13416329,"url":"https://github.com/ankane/authtrail","last_synced_at":"2025-11-17T14:03:22.546Z","repository":{"id":41262804,"uuid":"109928755","full_name":"ankane/authtrail","owner":"ankane","description":"Track Devise login activity","archived":false,"fork":false,"pushed_at":"2025-10-22T05:14:33.000Z","size":145,"stargazers_count":557,"open_issues_count":2,"forks_count":45,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-11-13T20:08:30.824Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ankane.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2017-11-08T04:46:11.000Z","updated_at":"2025-11-04T09:51:02.000Z","dependencies_parsed_at":"2024-02-17T19:28:47.137Z","dependency_job_id":"af4ccc2e-954e-46d6-92fa-2b6eba3895ba","html_url":"https://github.com/ankane/authtrail","commit_stats":{"total_commits":135,"total_committers":8,"mean_commits":16.875,"dds":0.6074074074074074,"last_synced_commit":"f2e91f52f55595687d44e83ffb921c76f4a2b8d9"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/ankane/authtrail","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Fauthtrail","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Fauthtrail/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Fauthtrail/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Fauthtrail/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ankane","download_url":"https://codeload.github.com/ankane/authtrail/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Fauthtrail/sbom","scorecard":{"id":197139,"data":{"date":"2025-08-11","repo":{"name":"github.com/ankane/authtrail","commit":"6799b514fbb557e5b5e6d8d9818360df31662000"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"1 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/build.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/ankane/authtrail/build.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/ankane/authtrail/build.yml/master?enable=pin","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-16T22:00:59.765Z","repository_id":41262804,"created_at":"2025-08-16T22:00:59.765Z","updated_at":"2025-08-16T22:00:59.765Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":284463932,"owners_count":27009532,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-14T02:00:06.101Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-30T21:00:57.159Z","updated_at":"2025-11-17T14:03:22.530Z","avatar_url":"https://github.com/ankane.png","language":"Ruby","funding_links":[],"categories":["Ruby","Authentication"],"sub_categories":[],"readme":"# AuthTrail\n\nTrack Devise login activity\n\n:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)\n\n[![Build Status](https://github.com/ankane/authtrail/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/authtrail/actions)\n\n## Installation\n\nAdd this line to your application’s Gemfile:\n\n```ruby\ngem \"authtrail\"\n```\n\nTo encrypt email and IP addresses with Lockbox, install [Lockbox](https://github.com/ankane/lockbox) and [Blind Index](https://github.com/ankane/blind_index) and run:\n\n```sh\nrails generate authtrail:install --encryption=lockbox\nrails db:migrate\n```\n\nTo use Active Record encryption, run:\n\n```sh\nrails generate authtrail:install --encryption=activerecord\nrails db:migrate\n```\n\nIf you prefer not to encrypt data, run:\n\n```sh\nrails generate authtrail:install --encryption=none\nrails db:migrate\n```\n\nTo enable geocoding, see the [Geocoding section](#geocoding).\n\n## How It Works\n\nA `LoginActivity` record is created every time a user tries to login. You can then use this information to detect suspicious behavior. Data includes:\n\n- `scope` - Devise scope\n- `strategy` - Devise strategy\n- `identity` - email address\n- `success` - whether the login succeeded\n- `failure_reason` - if the login failed\n- `user` - the user if the login succeeded\n- `context` - controller and action\n- `ip` - IP address\n- `user_agent` and `referrer` - from browser\n- `city`, `region`, `country`, `latitude`, and `longitude` - from IP\n- `created_at` - time of event\n\n## Features\n\nExclude certain attempts from tracking - useful if you run acceptance tests\n\n```ruby\nAuthTrail.exclude_method = lambda do |data|\n  data[:identity] == \"capybara@example.org\"\nend\n```\n\nAdd or modify data - also add new fields to the `login_activities` table if needed\n\n```ruby\nAuthTrail.transform_method = lambda do |data, request|\n  data[:request_id] = request.request_id\nend\n```\n\nStore the user on failed attempts\n\n```ruby\nAuthTrail.transform_method = lambda do |data, request|\n  data[:user] ||= User.find_by(email: data[:identity])\nend\n```\n\nWrite data somewhere other than the `login_activities` table\n\n```ruby\nAuthTrail.track_method = lambda do |data|\n  # code\nend\n```\n\nUse a custom identity method\n\n```ruby\nAuthTrail.identity_method = lambda do |request, opts, user|\n  if user\n    user.email\n  else\n    request.params.dig(opts[:scope], :email)\n  end\nend\n```\n\nAssociate login activity with your user model\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_many :login_activities, as: :user # use :user no matter what your model name\nend\n```\n\nThe `LoginActivity` model uses a [polymorphic association](https://guides.rubyonrails.org/association_basics.html#polymorphic-associations) so it can be associated with different user models.\n\n## Geocoding\n\nAuthTrail uses [Geocoder](https://github.com/alexreisner/geocoder) for geocoding. We recommend configuring [local geocoding](#local-geocoding) or [load balancer geocoding](#load-balancer-geocoding) so IP addresses are not sent to a 3rd party service. If you do use a 3rd party service and adhere to GDPR, be sure to add it to your subprocessor list.\n\nTo enable geocoding, add this line to your application’s Gemfile:\n\n```ruby\ngem \"geocoder\"\n```\n\nAnd update `config/initializers/authtrail.rb`:\n\n```ruby\nAuthTrail.geocode = true\n```\n\nGeocoding is performed in a background job so it doesn’t slow down web requests. Set the job queue with:\n\n```ruby\nAuthTrail.job_queue = :low_priority\n```\n\n### Local Geocoding\n\nFor privacy and performance, we recommend geocoding locally.\n\nFor city-level geocoding, download the [GeoLite2 City database](https://dev.maxmind.com/geoip/geoip2/geolite2/).\n\nAdd this line to your application’s Gemfile:\n\n```ruby\ngem \"maxminddb\"\n```\n\nAnd create `config/initializers/geocoder.rb` with:\n\n```ruby\nGeocoder.configure(\n  ip_lookup: :geoip2,\n  geoip2: {\n    file: \"path/to/GeoLite2-City.mmdb\"\n  }\n)\n```\n\nFor country-level geocoding, install the `geoip-database` package. It’s preinstalled on Heroku. For Ubuntu, use:\n\n```sh\nsudo apt-get install geoip-database\n```\n\nAdd this line to your application’s Gemfile:\n\n```ruby\ngem \"geoip\"\n```\n\nAnd create `config/initializers/geocoder.rb` with:\n\n```ruby\nGeocoder.configure(\n  ip_lookup: :maxmind_local,\n  maxmind_local: {\n    file: \"/usr/share/GeoIP/GeoIP.dat\",\n    package: :country\n  }\n)\n```\n\n### Load Balancer Geocoding\n\nSome load balancers can add geocoding information to request headers.\n\n- [nginx](https://nginx.org/en/docs/http/ngx_http_geoip_module.html)\n- [Google Cloud](https://cloud.google.com/load-balancing/docs/custom-headers)\n- [Cloudflare](https://support.cloudflare.com/hc/en-us/articles/200168236-Configuring-Cloudflare-IP-Geolocation)\n\n```ruby\nAuthTrail.geocode = false\n\nAuthTrail.transform_method = lambda do |data, request|\n  data[:country] = request.headers[\"\u003ccountry-header\u003e\"]\n  data[:region] = request.headers[\"\u003cregion-header\u003e\"]\n  data[:city] = request.headers[\"\u003ccity-header\u003e\"]\nend\n```\n\nCheck out [this example](https://github.com/ankane/authtrail/issues/40)\n\n## Data Retention\n\nDelete older data with:\n\n```ruby\nLoginActivity.where(\"created_at \u003c ?\", 2.years.ago).in_batches.delete_all\n```\n\nDelete data for a specific user with:\n\n```ruby\nLoginActivity.where(user_id: 1, user_type: \"User\").in_batches.delete_all\n```\n\n## Other Notes\n\nWe recommend using this in addition to Devise’s `Lockable` module and [Rack::Attack](https://github.com/kickstarter/rack-attack).\n\nCheck out [Hardening Devise](https://ankane.org/hardening-devise) and [Secure Rails](https://github.com/ankane/secure_rails) for more best practices.\n\n## History\n\nView the [changelog](https://github.com/ankane/authtrail/blob/master/CHANGELOG.md)\n\n## Contributing\n\nEveryone is encouraged to help improve this project. Here are a few ways you can help:\n\n- [Report bugs](https://github.com/ankane/authtrail/issues)\n- Fix bugs and [submit pull requests](https://github.com/ankane/authtrail/pulls)\n- Write, clarify, or fix documentation\n- Suggest or add new features\n\nTo get started with development and testing:\n\n```sh\ngit clone https://github.com/ankane/authtrail.git\ncd authtrail\nbundle install\nbundle exec rake test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fankane%2Fauthtrail","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fankane%2Fauthtrail","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fankane%2Fauthtrail/lists"}