authtrail 0.1.1 → 0.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d0affe4e892e8dac1a1ddc212ebb6834ef1de340f782de0f4a19dcc230e0a51
4
- data.tar.gz: 72b45a58e89181f07ea0c58e3875e67948287a2b1b0fc9aa627eac6be8aee5f8
3
+ metadata.gz: f88e2c20a95601d9da18766155a4eb0300ac193d7be16c56b8f293e42237163b
4
+ data.tar.gz: a74f7435a461fce5c2f2c12d98cc3329aeb6f45c6e54eac6192cd5d04dd1a857
5
5
  SHA512:
6
- metadata.gz: 25b90ec6e2059c37c405e3e647cc39c12d3ece6a12697ac0403f0ca4b07c7b288ff73194c6fe07af1fed558bd94feb1e870e267dfba5fd6abd8adf5e3a6e18a2
7
- data.tar.gz: 18428f49536b942f47d260e6d4251eaaae57c7cc6fddcb3d35fba1a3a6bb8adc8492453530569ab0c21b84181ffd2e92672ea8762ccb1c59dcb8c167c6037127
6
+ metadata.gz: 3a572225e8e080da90c400293ebccbb6a7808f642f2469b79e0777ed37470ccec13ebd31a6f6b57d4fc6b89d037f25c1ae1980298b68b04bdc258ff71037e578
7
+ data.tar.gz: 6558512fa9aa0b95932a95165c79533672f91c612116666cec6afe2743fcb7cd8357e9b230dadcab6ce503d8d8e622566c0d188d9b92cb016c37de5b46e3a09e
@@ -1,8 +1,31 @@
1
- ## 0.1.1
1
+ ## 0.2.2 (2020-11-21)
2
+
3
+ - Added `transform_method` option
4
+
5
+ ## 0.2.1 (2020-08-17)
6
+
7
+ - Added `job_queue` option
8
+
9
+ ## 0.2.0 (2019-06-23)
10
+
11
+ - Added latitude and longitude
12
+ - `AuthTrail::GeocodeJob` now inherits from `ActiveJob::Base` instead of `ApplicationJob`
13
+ - Removed support for Rails 4.2
14
+
15
+ ## 0.1.3 (2018-09-27)
16
+
17
+ - Added support for Rails 4.2
18
+
19
+ ## 0.1.2 (2018-07-30)
20
+
21
+ - Added `identity_method` option
22
+ - Fixed geocoding
23
+
24
+ ## 0.1.1 (2018-07-13)
2
25
 
3
26
  - Improved strategy detection for failures
4
27
  - Fixed migration for MySQL
5
28
 
6
- ## 0.1.0
29
+ ## 0.1.0 (2017-11-07)
7
30
 
8
31
  - First release
@@ -1,4 +1,4 @@
1
- Copyright (c) 2017 Andrew Kane
1
+ Copyright (c) 2017-2020 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -4,6 +4,8 @@ Track Devise login activity
4
4
 
5
5
  :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
6
6
 
7
+ [![Build Status](https://github.com/ankane/authtrail/workflows/build/badge.svg?branch=master)](https://github.com/ankane/authtrail/actions)
8
+
7
9
  ## Installation
8
10
 
9
11
  Add this line to your application’s Gemfile:
@@ -16,7 +18,7 @@ And run:
16
18
 
17
19
  ```sh
18
20
  rails generate authtrail:install
19
- rake db:migrate
21
+ rails db:migrate
20
22
  ```
21
23
 
22
24
  ## How It Works
@@ -32,44 +34,127 @@ A `LoginActivity` record is created every time a user tries to login. You can th
32
34
  - `context` - controller and action
33
35
  - `ip` - IP address
34
36
  - `user_agent` and `referrer` - from browser
35
- - `city`, `region`, and `country` - from IP
37
+ - `city`, `region`, `country`, `latitude`, and `longitude` - from IP
36
38
  - `created_at` - time of event
37
39
 
38
- IP geocoding is performed in a background job so it doesn’t slow down web requests. You can disable it entirely with:
40
+ ## Features
41
+
42
+ Exclude certain attempts from tracking - useful if you run acceptance tests
39
43
 
40
44
  ```ruby
41
- AuthTrail.geocode = false
45
+ AuthTrail.exclude_method = lambda do |data|
46
+ data[:identity] == "capybara@example.org"
47
+ end
42
48
  ```
43
49
 
44
- ## Features
50
+ Add or modify data (also add new fields to the `login_activities` table)
45
51
 
46
- Exclude certain attempts from tracking - useful if you run acceptance tests
52
+ ```ruby
53
+ AuthTrail.transform_method = lambda do |data, request|
54
+ data[:request_id] = request.request_id
55
+ end
56
+ ```
57
+
58
+ Store the user on failed attempts
47
59
 
48
60
  ```ruby
49
- AuthTrail.exclude_method = proc do |info|
50
- info[:identity] == "capybara@example.org"
61
+ AuthTrail.transform_method = lambda do |data, request|
62
+ data[:user] ||= User.find_by(email: data[:identity])
51
63
  end
52
64
  ```
53
65
 
54
- Write data somewhere other than the `login_activities` table.
66
+ Write data somewhere other than the `login_activities` table
55
67
 
56
68
  ```ruby
57
- AuthTrail.track_method = proc do |info|
69
+ AuthTrail.track_method = lambda do |data|
58
70
  # code
59
71
  end
60
72
  ```
61
73
 
74
+ Use a custom identity method
75
+
76
+ ```ruby
77
+ AuthTrail.identity_method = lambda do |request, opts, user|
78
+ if user
79
+ user.email
80
+ else
81
+ request.params.dig(opts[:scope], :email)
82
+ end
83
+ end
84
+ ```
85
+
86
+ Associate login activity with your user model
87
+
88
+ ```ruby
89
+ class User < ApplicationRecord
90
+ has_many :login_activities, as: :user # use :user no matter what your model name
91
+ end
92
+ ```
93
+
94
+ The `LoginActivity` model uses a [polymorphic association](https://guides.rubyonrails.org/association_basics.html#polymorphic-associations) so it can be associated with different user models.
95
+
96
+ ## Geocoding
97
+
98
+ IP geocoding is performed in a background job so it doesn’t slow down web requests. You can disable it entirely with:
99
+
100
+ ```ruby
101
+ AuthTrail.geocode = false
102
+ ```
103
+
62
104
  Set job queue for geocoding
63
105
 
64
106
  ```ruby
65
- AuthTrail::GeocodeJob.queue_as :low
107
+ AuthTrail.job_queue = :low_priority
108
+ ```
109
+
110
+ ### Geocoding Performance
111
+
112
+ To avoid calls to a remote API, download the [GeoLite2 City database](https://dev.maxmind.com/geoip/geoip2/geolite2/) and configure Geocoder to use it.
113
+
114
+ Add this line to your application’s Gemfile:
115
+
116
+ ```ruby
117
+ gem 'maxminddb'
118
+ ```
119
+
120
+ And create an initializer at `config/initializers/geocoder.rb` with:
121
+
122
+ ```ruby
123
+ Geocoder.configure(
124
+ ip_lookup: :geoip2,
125
+ geoip2: {
126
+ file: Rails.root.join("lib", "GeoLite2-City.mmdb")
127
+ }
128
+ )
129
+ ```
130
+
131
+ ## Data Protection
132
+
133
+ Protect the privacy of your users by encrypting fields that contain personal data, such as `identity` and `ip`. [Lockbox](https://github.com/ankane/lockbox) is great for this. Use [Blind Index](https://github.com/ankane/blind_index) so you can still query the fields.
134
+
135
+ ```ruby
136
+ class LoginActivity < ApplicationRecord
137
+ encrypts :identity, :ip
138
+ blind_index :identity, :ip
139
+ end
66
140
  ```
67
141
 
68
142
  ## Other Notes
69
143
 
70
144
  We recommend using this in addition to Devise’s `Lockable` module and [Rack::Attack](https://github.com/kickstarter/rack-attack).
71
145
 
72
- Works with Rails 5+
146
+ Check out [Hardening Devise](https://ankane.org/hardening-devise) and [Secure Rails](https://github.com/ankane/secure_rails) for more best practices.
147
+
148
+ ## Upgrading
149
+
150
+ ### 0.2.0
151
+
152
+ To store latitude and longitude, create a migration with:
153
+
154
+ ```ruby
155
+ add_column :login_activities, :latitude, :float
156
+ add_column :login_activities, :longitude, :float
157
+ ```
73
158
 
74
159
  ## History
75
160
 
@@ -83,3 +168,12 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
83
168
  - Fix bugs and [submit pull requests](https://github.com/ankane/authtrail/pulls)
84
169
  - Write, clarify, or fix documentation
85
170
  - Suggest or add new features
171
+
172
+ To get started with development and testing:
173
+
174
+ ```sh
175
+ git clone https://github.com/ankane/authtrail.git
176
+ cd authtrail
177
+ bundle install
178
+ bundle exec rake test
179
+ ```
@@ -1,20 +1,30 @@
1
1
  module AuthTrail
2
- class GeocodeJob < ApplicationJob
2
+ class GeocodeJob < ActiveJob::Base
3
+ # default queue is used if queue_as returns nil
4
+ # Rails has a test for this
5
+ queue_as { AuthTrail.job_queue }
6
+
3
7
  def perform(login_activity)
4
8
  result =
5
9
  begin
6
- Geocoder.search(login_activity.ip).first.try(:data)
10
+ Geocoder.search(login_activity.ip).first
7
11
  rescue => e
8
12
  Rails.logger.info "Geocode failed: #{e.message}"
9
13
  nil
10
14
  end
11
15
 
12
16
  if result
13
- login_activity.update!(
14
- city: result["city"].presence,
15
- region: result["region_name"].presence,
16
- country: result["country_name"].presence
17
- )
17
+ attributes = {
18
+ city: result.try(:city),
19
+ region: result.try(:state),
20
+ country: result.try(:country),
21
+ latitude: result.try(:latitude),
22
+ longitude: result.try(:longitude)
23
+ }
24
+ attributes.each do |k, v|
25
+ login_activity.try("#{k}=", v.presence)
26
+ end
27
+ login_activity.save!
18
28
  end
19
29
  end
20
30
  end
@@ -6,12 +6,10 @@ module AuthTrail
6
6
  AuthTrail.safely do
7
7
  request = ActionDispatch::Request.new(auth.env)
8
8
 
9
- identity = user.try(:email)
10
-
11
9
  AuthTrail.track(
12
10
  strategy: detect_strategy(auth),
13
11
  scope: opts[:scope].to_s,
14
- identity: identity,
12
+ identity: AuthTrail.identity_method.call(request, opts, user),
15
13
  success: true,
16
14
  request: request,
17
15
  user: user
@@ -21,21 +19,16 @@ module AuthTrail
21
19
 
22
20
  def before_failure(env, opts)
23
21
  AuthTrail.safely do
24
- if opts[:message]
25
- request = ActionDispatch::Request.new(env)
26
-
27
- scope = opts[:scope]
28
- identity = request.params[scope] && request.params[scope][:email] rescue nil
22
+ request = ActionDispatch::Request.new(env)
29
23
 
30
- AuthTrail.track(
31
- strategy: detect_strategy(env["warden"]),
32
- scope: scope.to_s,
33
- identity: identity,
34
- success: false,
35
- request: request,
36
- failure_reason: opts[:message].to_s
37
- )
38
- end
24
+ AuthTrail.track(
25
+ strategy: detect_strategy(env["warden"]),
26
+ scope: opts[:scope].to_s,
27
+ identity: AuthTrail.identity_method.call(request, opts, nil),
28
+ success: false,
29
+ request: request,
30
+ failure_reason: opts[:message].to_s
31
+ )
39
32
  end
40
33
  end
41
34
 
@@ -1,3 +1,3 @@
1
1
  module AuthTrail
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.2"
3
3
  end
@@ -9,32 +9,51 @@ require "auth_trail/version"
9
9
 
10
10
  module AuthTrail
11
11
  class << self
12
- attr_accessor :exclude_method, :geocode, :track_method
12
+ attr_accessor :exclude_method, :geocode, :track_method, :identity_method, :job_queue, :transform_method
13
13
  end
14
14
  self.geocode = true
15
+ self.identity_method = lambda do |request, opts, user|
16
+ if user
17
+ user.try(:email)
18
+ else
19
+ scope = opts[:scope]
20
+ request.params[scope] && request.params[scope][:email] rescue nil
21
+ end
22
+ end
15
23
 
16
24
  def self.track(strategy:, scope:, identity:, success:, request:, user: nil, failure_reason: nil)
17
- info = {
25
+ data = {
18
26
  strategy: strategy,
19
27
  scope: scope,
20
28
  identity: identity,
21
29
  success: success,
22
30
  failure_reason: failure_reason,
23
31
  user: user,
24
- context: "#{request.params[:controller]}##{request.params[:action]}",
25
32
  ip: request.remote_ip,
26
33
  user_agent: request.user_agent,
27
34
  referrer: request.referrer
28
35
  }
29
36
 
37
+ if request.params[:controller]
38
+ data[:context] = "#{request.params[:controller]}##{request.params[:action]}"
39
+ end
40
+
41
+ # add request data before exclude_method since exclude_method doesn't have access to request
42
+ # could also add 2nd argument to exclude_method when arity > 1
43
+ AuthTrail.transform_method.call(data, request) if AuthTrail.transform_method
44
+
30
45
  # if exclude_method throws an exception, default to not excluding
31
- exclude = AuthTrail.exclude_method && AuthTrail.safely(default: false) { AuthTrail.exclude_method.call(info) }
46
+ exclude = AuthTrail.exclude_method && AuthTrail.safely(default: false) { AuthTrail.exclude_method.call(data) }
32
47
 
33
48
  unless exclude
34
49
  if AuthTrail.track_method
35
- AuthTrail.track_method.call(info)
50
+ AuthTrail.track_method.call(data)
36
51
  else
37
- login_activity = LoginActivity.create!(info)
52
+ login_activity = LoginActivity.new
53
+ data.each do |k, v|
54
+ login_activity.try("#{k}=", v)
55
+ end
56
+ login_activity.save!
38
57
  AuthTrail::GeocodeJob.perform_later(login_activity) if AuthTrail.geocode
39
58
  end
40
59
  end
@@ -55,5 +74,5 @@ Warden::Manager.after_set_user except: :fetch do |user, auth, opts|
55
74
  end
56
75
 
57
76
  Warden::Manager.before_failure do |env, opts|
58
- AuthTrail::Manager.before_failure(env, opts)
77
+ AuthTrail::Manager.before_failure(env, opts) if opts[:message]
59
78
  end
@@ -1,24 +1,10 @@
1
- # taken from https://github.com/collectiveidea/audited/blob/master/lib/generators/audited/install_generator.rb
2
- require "rails/generators"
3
- require "rails/generators/migration"
4
- require "active_record"
5
1
  require "rails/generators/active_record"
6
2
 
7
3
  module Authtrail
8
4
  module Generators
9
5
  class InstallGenerator < Rails::Generators::Base
10
- include Rails::Generators::Migration
11
- source_root File.expand_path("../templates", __FILE__)
12
-
13
- # Implement the required interface for Rails::Generators::Migration.
14
- def self.next_migration_number(dirname) #:nodoc:
15
- next_migration_number = current_migration_number(dirname) + 1
16
- if ::ActiveRecord::Base.timestamped_migrations
17
- [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
18
- else
19
- "%.3d" % next_migration_number
20
- end
21
- end
6
+ include ActiveRecord::Generators::Migration
7
+ source_root File.join(__dir__, "templates")
22
8
 
23
9
  def copy_migration
24
10
  migration_template "login_activities_migration.rb", "db/migrate/create_login_activities.rb", migration_version: migration_version
@@ -1,19 +1,21 @@
1
1
  class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
2
  def change
3
3
  create_table :login_activities do |t|
4
- t.text :scope
5
- t.text :strategy
4
+ t.string :scope
5
+ t.string :strategy
6
6
  t.string :identity
7
7
  t.boolean :success
8
- t.text :failure_reason
8
+ t.string :failure_reason
9
9
  t.references :user, polymorphic: true
10
- t.text :context
10
+ t.string :context
11
11
  t.string :ip
12
12
  t.text :user_agent
13
13
  t.text :referrer
14
- t.text :city
15
- t.text :region
16
- t.text :country
14
+ t.string :city
15
+ t.string :region
16
+ t.string :country
17
+ t.float :latitude
18
+ t.float :longitude
17
19
  t.datetime :created_at
18
20
  end
19
21
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authtrail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
- autorequire:
9
- bindir: exe
8
+ autorequire:
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-13 00:00:00.000000000 Z
11
+ date: 2020-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -96,6 +96,20 @@ dependencies:
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: minitest
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '5'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '5'
111
+ - !ruby/object:Gem::Dependency
112
+ name: combustion
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
115
  - - ">="
@@ -108,32 +122,70 @@ dependencies:
108
122
  - - ">="
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0'
111
- description:
112
- email:
113
- - andrew@chartkick.com
125
+ - !ruby/object:Gem::Dependency
126
+ name: rails
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: sqlite3
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: devise
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description:
168
+ email: andrew@chartkick.com
114
169
  executables: []
115
170
  extensions: []
116
171
  extra_rdoc_files: []
117
172
  files:
118
- - ".gitignore"
119
173
  - CHANGELOG.md
120
- - Gemfile
121
174
  - LICENSE.txt
122
175
  - README.md
123
- - Rakefile
124
176
  - app/jobs/auth_trail/geocode_job.rb
125
- - authtrail.gemspec
126
177
  - lib/auth_trail/engine.rb
127
178
  - lib/auth_trail/manager.rb
128
179
  - lib/auth_trail/version.rb
129
180
  - lib/authtrail.rb
130
181
  - lib/generators/authtrail/install_generator.rb
131
- - lib/generators/authtrail/templates/login_activities_migration.rb
132
- - lib/generators/authtrail/templates/login_activity_model.rb
182
+ - lib/generators/authtrail/templates/login_activities_migration.rb.tt
183
+ - lib/generators/authtrail/templates/login_activity_model.rb.tt
133
184
  homepage: https://github.com/ankane/authtrail
134
- licenses: []
185
+ licenses:
186
+ - MIT
135
187
  metadata: {}
136
- post_install_message:
188
+ post_install_message:
137
189
  rdoc_options: []
138
190
  require_paths:
139
191
  - lib
@@ -141,16 +193,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
141
193
  requirements:
142
194
  - - ">="
143
195
  - !ruby/object:Gem::Version
144
- version: '0'
196
+ version: '2.4'
145
197
  required_rubygems_version: !ruby/object:Gem::Requirement
146
198
  requirements:
147
199
  - - ">="
148
200
  - !ruby/object:Gem::Version
149
201
  version: '0'
150
202
  requirements: []
151
- rubyforge_project:
152
- rubygems_version: 2.7.7
153
- signing_key:
203
+ rubygems_version: 3.1.4
204
+ signing_key:
154
205
  specification_version: 4
155
206
  summary: Track Devise login activity
156
207
  test_files: []
data/.gitignore DELETED
@@ -1,9 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
- Gemfile.lock
data/Gemfile DELETED
@@ -1,6 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
-
5
- # Specify your gem's dependencies in authtrail.gemspec
6
- gemspec
data/Rakefile DELETED
@@ -1,10 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
3
-
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
8
- end
9
-
10
- task default: :test
@@ -1,30 +0,0 @@
1
-
2
- lib = File.expand_path("../lib", __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "auth_trail/version"
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "authtrail"
8
- spec.version = AuthTrail::VERSION
9
- spec.authors = ["Andrew Kane"]
10
- spec.email = ["andrew@chartkick.com"]
11
-
12
- spec.summary = "Track Devise login activity"
13
- spec.homepage = "https://github.com/ankane/authtrail"
14
-
15
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
- f.match(%r{^(test|spec|features)/})
17
- end
18
- spec.bindir = "exe"
19
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
- spec.require_paths = ["lib"]
21
-
22
- spec.add_dependency "railties", ">= 5"
23
- spec.add_dependency "activerecord", ">= 5"
24
- spec.add_dependency "warden"
25
- spec.add_dependency "geocoder"
26
-
27
- spec.add_development_dependency "bundler"
28
- spec.add_development_dependency "rake"
29
- spec.add_development_dependency "minitest"
30
- end