authtrail 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d09fb569715557b1fcde80bcab0990674c3950c3
4
+ data.tar.gz: 4507a5bfb4536803efcc1a8d29ef04c1dd4c0717
5
+ SHA512:
6
+ metadata.gz: 12879861a9e98af5c5defd08ddf1f10d0aa11e04724ff56dd191a34b3ea778821e8173a4198f1221a92774db49301e8e633f064fdbc7385a7f184df7173de00d
7
+ data.tar.gz: 1777a6f8bd28435970616e6dc90937301354edeb89ffb5ada0c85c531e01b07e8f138959f7313dbbd0c7ccb7add148550743425b5f90f200bfad7ca352363ad9
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
@@ -0,0 +1,3 @@
1
+ ## 0.1.0
2
+
3
+ - First release
data/Gemfile ADDED
@@ -0,0 +1,6 @@
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
@@ -0,0 +1,125 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ authtrail (0.1.0)
5
+ geocoder
6
+ rails (>= 5)
7
+ warden
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ actioncable (5.1.4)
13
+ actionpack (= 5.1.4)
14
+ nio4r (~> 2.0)
15
+ websocket-driver (~> 0.6.1)
16
+ actionmailer (5.1.4)
17
+ actionpack (= 5.1.4)
18
+ actionview (= 5.1.4)
19
+ activejob (= 5.1.4)
20
+ mail (~> 2.5, >= 2.5.4)
21
+ rails-dom-testing (~> 2.0)
22
+ actionpack (5.1.4)
23
+ actionview (= 5.1.4)
24
+ activesupport (= 5.1.4)
25
+ rack (~> 2.0)
26
+ rack-test (>= 0.6.3)
27
+ rails-dom-testing (~> 2.0)
28
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
29
+ actionview (5.1.4)
30
+ activesupport (= 5.1.4)
31
+ builder (~> 3.1)
32
+ erubi (~> 1.4)
33
+ rails-dom-testing (~> 2.0)
34
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
35
+ activejob (5.1.4)
36
+ activesupport (= 5.1.4)
37
+ globalid (>= 0.3.6)
38
+ activemodel (5.1.4)
39
+ activesupport (= 5.1.4)
40
+ activerecord (5.1.4)
41
+ activemodel (= 5.1.4)
42
+ activesupport (= 5.1.4)
43
+ arel (~> 8.0)
44
+ activesupport (5.1.4)
45
+ concurrent-ruby (~> 1.0, >= 1.0.2)
46
+ i18n (~> 0.7)
47
+ minitest (~> 5.1)
48
+ tzinfo (~> 1.1)
49
+ arel (8.0.0)
50
+ builder (3.2.3)
51
+ concurrent-ruby (1.0.5)
52
+ crass (1.0.2)
53
+ erubi (1.7.0)
54
+ geocoder (1.4.4)
55
+ globalid (0.4.1)
56
+ activesupport (>= 4.2.0)
57
+ i18n (0.9.1)
58
+ concurrent-ruby (~> 1.0)
59
+ loofah (2.1.1)
60
+ crass (~> 1.0.2)
61
+ nokogiri (>= 1.5.9)
62
+ mail (2.7.0)
63
+ mini_mime (>= 0.1.1)
64
+ method_source (0.9.0)
65
+ mini_mime (0.1.4)
66
+ mini_portile2 (2.3.0)
67
+ minitest (5.10.3)
68
+ nio4r (2.1.0)
69
+ nokogiri (1.8.1)
70
+ mini_portile2 (~> 2.3.0)
71
+ rack (2.0.3)
72
+ rack-test (0.7.0)
73
+ rack (>= 1.0, < 3)
74
+ rails (5.1.4)
75
+ actioncable (= 5.1.4)
76
+ actionmailer (= 5.1.4)
77
+ actionpack (= 5.1.4)
78
+ actionview (= 5.1.4)
79
+ activejob (= 5.1.4)
80
+ activemodel (= 5.1.4)
81
+ activerecord (= 5.1.4)
82
+ activesupport (= 5.1.4)
83
+ bundler (>= 1.3.0)
84
+ railties (= 5.1.4)
85
+ sprockets-rails (>= 2.0.0)
86
+ rails-dom-testing (2.0.3)
87
+ activesupport (>= 4.2.0)
88
+ nokogiri (>= 1.6)
89
+ rails-html-sanitizer (1.0.3)
90
+ loofah (~> 2.0)
91
+ railties (5.1.4)
92
+ actionpack (= 5.1.4)
93
+ activesupport (= 5.1.4)
94
+ method_source
95
+ rake (>= 0.8.7)
96
+ thor (>= 0.18.1, < 2.0)
97
+ rake (12.2.1)
98
+ sprockets (3.7.1)
99
+ concurrent-ruby (~> 1.0)
100
+ rack (> 1, < 3)
101
+ sprockets-rails (3.2.1)
102
+ actionpack (>= 4.0)
103
+ activesupport (>= 4.0)
104
+ sprockets (>= 3.0.0)
105
+ thor (0.20.0)
106
+ thread_safe (0.3.6)
107
+ tzinfo (1.2.4)
108
+ thread_safe (~> 0.1)
109
+ warden (1.2.7)
110
+ rack (>= 1.0)
111
+ websocket-driver (0.6.5)
112
+ websocket-extensions (>= 0.1.0)
113
+ websocket-extensions (0.1.2)
114
+
115
+ PLATFORMS
116
+ ruby
117
+
118
+ DEPENDENCIES
119
+ authtrail!
120
+ bundler
121
+ minitest
122
+ rake
123
+
124
+ BUNDLED WITH
125
+ 1.16.0
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2017 Andrew Kane
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,85 @@
1
+ # AuthTrail
2
+
3
+ Track Devise login activity
4
+
5
+ :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application’s Gemfile:
10
+
11
+ ```ruby
12
+ gem 'authtrail'
13
+ ```
14
+
15
+ And run:
16
+
17
+ ```sh
18
+ rails generate authtrail:install
19
+ rake db:migrate
20
+ ```
21
+
22
+ ## How It Works
23
+
24
+ A `LoginActivity` record is created every time a user tries to login. You can then use this information to detect suspicious behavior. Data includes:
25
+
26
+ - `scope` - Devise scope
27
+ - `strategy` - `database_authenticatable` for password logins, `rememberable` for remember me cookie, or the name of the OmniAuth strategy
28
+ - `identity` - email address
29
+ - `success` - whether the login succeeded
30
+ - `failure_reason` - if the login failed
31
+ - `user` - the user if the login succeeded
32
+ - `context` - controller and action
33
+ - `ip` - IP address
34
+ - `user_agent` and `referrer` - from browser
35
+ - `city`, `region`, and `country` - from IP
36
+ - `created_at` - time of event
37
+
38
+ IP geocoding is performed in a background job so it doesn’t slow down web requests. You can disable it entirely with:
39
+
40
+ ```ruby
41
+ AuthTrail.geocode = false
42
+ ```
43
+
44
+ ## Features
45
+
46
+ Exclude certain attempts from tracking - useful if you run acceptance tests
47
+
48
+ ```ruby
49
+ AuthTrail.exclude_method = proc do |info|
50
+ info[:identity] == "capybara@example.org"
51
+ end
52
+ ```
53
+
54
+ Write data somewhere other than the `login_activities` table.
55
+
56
+ ```ruby
57
+ AuthTrail.track_method = proc do |info|
58
+ # code
59
+ end
60
+ ```
61
+
62
+ Set job queue for geocoding
63
+
64
+ ```ruby
65
+ AuthTrail::GeocodeJob.queue_as :low
66
+ ```
67
+
68
+ ## Other Notes
69
+
70
+ We recommend using this in addition to Devise’s `Lockable` module and [Rack::Attack](https://github.com/kickstarter/rack-attack).
71
+
72
+ Works with Rails 5+
73
+
74
+ ## History
75
+
76
+ View the [changelog](https://github.com/ankane/authtrail/blob/master/CHANGELOG.md)
77
+
78
+ ## Contributing
79
+
80
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
81
+
82
+ - [Report bugs](https://github.com/ankane/authtrail/issues)
83
+ - Fix bugs and [submit pull requests](https://github.com/ankane/authtrail/pulls)
84
+ - Write, clarify, or fix documentation
85
+ - Suggest or add new features
@@ -0,0 +1,10 @@
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
@@ -0,0 +1,21 @@
1
+ module AuthTrail
2
+ class GeocodeJob < ApplicationJob
3
+ def perform(login_activity)
4
+ result =
5
+ begin
6
+ Geocoder.search(login_activity.ip).first.try(:data)
7
+ rescue => e
8
+ Rails.logger.info "Geocode failed: #{e.message}"
9
+ nil
10
+ end
11
+
12
+ if result
13
+ login_activity.update!(
14
+ city: result["city"].presence,
15
+ region: result["region_name"].presence,
16
+ country: result["country_name"].presence
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
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 "rails", ">= 5"
23
+ spec.add_dependency "warden"
24
+ spec.add_dependency "geocoder"
25
+
26
+ spec.add_development_dependency "bundler"
27
+ spec.add_development_dependency "rake"
28
+ spec.add_development_dependency "minitest"
29
+ end
@@ -0,0 +1,4 @@
1
+ module AuthTrail
2
+ class Engine < Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,44 @@
1
+ module AuthTrail
2
+ module Manager
3
+ class << self
4
+ def after_set_user(user, auth, opts)
5
+ # do not raise an exception for tracking
6
+ AuthTrail.safely do
7
+ request = ActionDispatch::Request.new(auth.env)
8
+
9
+ strategy = auth.env["omniauth.auth"]["provider"] if auth.env["omniauth.auth"]
10
+ strategy ||= auth.winning_strategy.class.name.split("::").last.underscore if auth.winning_strategy
11
+ strategy ||= "database_authenticatable"
12
+
13
+ identity = user.try(:email)
14
+ AuthTrail.track(
15
+ strategy: strategy,
16
+ scope: opts[:scope].to_s,
17
+ identity: identity,
18
+ success: true,
19
+ request: request,
20
+ user: user
21
+ )
22
+ end
23
+ end
24
+
25
+ def before_failure(env, opts)
26
+ AuthTrail.safely do
27
+ if opts[:message]
28
+ request = ActionDispatch::Request.new(env)
29
+ identity = request.params[opts[:scope]] && request.params[opts[:scope]][:email] rescue nil
30
+
31
+ AuthTrail.track(
32
+ strategy: "database_authenticatable",
33
+ scope: opts[:scope].to_s,
34
+ identity: identity,
35
+ success: false,
36
+ request: request,
37
+ failure_reason: opts[:message].to_s
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module AuthTrail
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,57 @@
1
+ require "geocoder"
2
+ require "rails"
3
+ require "warden"
4
+ require "auth_trail/engine"
5
+ require "auth_trail/manager"
6
+ require "auth_trail/version"
7
+
8
+ module AuthTrail
9
+ class << self
10
+ attr_accessor :exclude_method, :geocode, :track_method
11
+ end
12
+ self.geocode = true
13
+
14
+ def self.track(strategy:, scope:, identity:, success:, request:, user: nil, failure_reason: nil)
15
+ info = {
16
+ strategy: strategy,
17
+ scope: scope,
18
+ identity: identity,
19
+ success: success,
20
+ failure_reason: failure_reason,
21
+ user: user,
22
+ context: "#{request.params[:controller]}##{request.params[:action]}",
23
+ ip: request.remote_ip,
24
+ user_agent: request.user_agent,
25
+ referrer: request.referrer
26
+ }
27
+
28
+ # if exclude_method throws an exception, default to not excluding
29
+ exclude = AuthTrail.exclude_method && AuthTrail.safely(default: false) { AuthTrail.exclude_method.call(info) }
30
+
31
+ unless exclude
32
+ if AuthTrail.track_method
33
+ AuthTrail.track_method.call(info)
34
+ else
35
+ login_activity = LoginActivity.create!(info)
36
+ AuthTrail::GeocodeJob.perform_later(login_activity) if AuthTrail.geocode
37
+ end
38
+ end
39
+ end
40
+
41
+ def self.safely(default: nil)
42
+ begin
43
+ yield
44
+ rescue => e
45
+ warn "[authtrail] #{e.class.name}: #{e.message}"
46
+ default
47
+ end
48
+ end
49
+ end
50
+
51
+ Warden::Manager.after_set_user except: :fetch do |user, auth, opts|
52
+ AuthTrail::Manager.after_set_user(user, auth, opts)
53
+ end
54
+
55
+ Warden::Manager.before_failure do |env, opts|
56
+ AuthTrail::Manager.before_failure(env, opts)
57
+ end
@@ -0,0 +1,36 @@
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
+ require "rails/generators/active_record"
6
+
7
+ module Authtrail
8
+ module Generators
9
+ 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
22
+
23
+ def copy_migration
24
+ migration_template "login_activities_migration.rb", "db/migrate/create_login_activities.rb", migration_version: migration_version
25
+ end
26
+
27
+ def generate_model
28
+ template "login_activity_model.rb", "app/models/login_activity.rb"
29
+ end
30
+
31
+ def migration_version
32
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :login_activities do |t|
4
+ t.text :scope
5
+ t.text :strategy
6
+ t.text :identity
7
+ t.boolean :success
8
+ t.text :failure_reason
9
+ t.references :user, polymorphic: true
10
+ t.text :context
11
+ t.text :ip
12
+ t.text :user_agent
13
+ t.text :referrer
14
+ t.text :city
15
+ t.text :region
16
+ t.text :country
17
+ t.datetime :created_at
18
+ end
19
+
20
+ add_index :login_activities, :identity
21
+ add_index :login_activities, :ip
22
+ add_index :login_activities, :user_id
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ class LoginActivity < ApplicationRecord
2
+ belongs_to :user, polymorphic: true, optional: true
3
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: authtrail
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Kane
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-11-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: warden
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: geocoder
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - andrew@chartkick.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - CHANGELOG.md
106
+ - Gemfile
107
+ - Gemfile.lock
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - app/jobs/auth_trail/geocode_job.rb
112
+ - authtrail.gemspec
113
+ - lib/auth_trail/engine.rb
114
+ - lib/auth_trail/manager.rb
115
+ - lib/auth_trail/version.rb
116
+ - lib/authtrail.rb
117
+ - lib/generators/authtrail/install_generator.rb
118
+ - lib/generators/authtrail/templates/login_activities_migration.rb
119
+ - lib/generators/authtrail/templates/login_activity_model.rb
120
+ homepage: https://github.com/ankane/authtrail
121
+ licenses: []
122
+ metadata: {}
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 2.6.13
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: Track Devise login activity
143
+ test_files: []