log_logins 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +2 -0
- data/db/migrate/20180307100300_create_log_logins_events_table.rb +26 -0
- data/lib/log_logins/config.rb +52 -0
- data/lib/log_logins/engine.rb +7 -0
- data/lib/log_logins/error.rb +18 -0
- data/lib/log_logins/event.rb +95 -0
- data/lib/log_logins/user.rb +11 -0
- data/lib/log_logins/version.rb +5 -0
- data/lib/log_logins.rb +59 -0
- data.tar.gz.sig +1 -0
- metadata +93 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 21a9dcc5a4f82db3ca896fb29adce696076a910519d27f1806e64d7c6b0b0ae1
|
4
|
+
data.tar.gz: 8f30718d34d660a686c1635ddcf87952d9dd652b425e2aeea3ef70a0331aa236
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1f97f1daa840b5edb00a4fa8ce744c7608aa0a37485b57e48a537dfbb1aac67042e6ab26173eff2788dc3433aae2e01fa16c1343b4b495b576be3823bc99ac14
|
7
|
+
data.tar.gz: 4be1790c473c2a72e6a4ebe84d7c9dac77d5f2a7e4c1293da7e0b8ad0af4e997e5adb0c6c8cd8e5167227aa717abe84046cbd84eaa2c422a34a97df3622571ac
|
checksums.yaml.gz.sig
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
class CreateLogLoginsEventsTable < ActiveRecord::Migration[4.2]
|
2
|
+
|
3
|
+
def change
|
4
|
+
create_table :login_events do |t|
|
5
|
+
t.string :user_type # User, APIToken
|
6
|
+
t.integer :user_id # 141
|
7
|
+
|
8
|
+
t.string :username
|
9
|
+
|
10
|
+
t.string :action # Success, Failed, Blocked
|
11
|
+
t.string :interface # Web, API, SomeOtherInterface
|
12
|
+
t.string :ip # 1.2.3.4
|
13
|
+
t.string :user_agent
|
14
|
+
|
15
|
+
t.datetime :last_attempt_at
|
16
|
+
|
17
|
+
t.datetime :created_at
|
18
|
+
|
19
|
+
t.index [:user_type, :user_id], :length => {:user_type => 10}
|
20
|
+
t.index :ip, :length => 10
|
21
|
+
t.index :interface, :length => 10
|
22
|
+
t.index :created_at
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LogLogins
|
4
|
+
|
5
|
+
def self.config
|
6
|
+
@config ||= Config.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.configure(&block)
|
10
|
+
block.call(config)
|
11
|
+
config
|
12
|
+
end
|
13
|
+
|
14
|
+
class Config
|
15
|
+
|
16
|
+
attr_reader :callbacks
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@callbacks = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def on(event, &block)
|
23
|
+
@callbacks[event.to_sym] ||= []
|
24
|
+
@callbacks[event.to_sym] << block
|
25
|
+
end
|
26
|
+
|
27
|
+
def remove_callback(event, block)
|
28
|
+
@callbacks[event.to_sym] && @callbacks[event.to_sym].delete(block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def events_table_name
|
32
|
+
@events_table_name || 'login_events'
|
33
|
+
end
|
34
|
+
attr_writer :events_table_name
|
35
|
+
|
36
|
+
def block_time
|
37
|
+
@block_time || 3600
|
38
|
+
end
|
39
|
+
attr_writer :block_time
|
40
|
+
|
41
|
+
def attempts_before_block
|
42
|
+
@attempts_before_block || 10
|
43
|
+
end
|
44
|
+
attr_writer :attempts_before_block
|
45
|
+
|
46
|
+
def attempts_before_block_on_ip
|
47
|
+
@attempts_before_block_on_ip || attempts_before_block
|
48
|
+
end
|
49
|
+
attr_writer :attempts_before_block_on_ip
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LogLogins
|
4
|
+
class Error < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class InvalidLogEventError < Error
|
8
|
+
end
|
9
|
+
|
10
|
+
class LoginBlocked < Error
|
11
|
+
attr_accessor :event
|
12
|
+
|
13
|
+
def initialize(event)
|
14
|
+
@event = event
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
require 'log_logins/config'
|
5
|
+
require 'log_logins/error'
|
6
|
+
|
7
|
+
module LogLogins
|
8
|
+
class Event < ActiveRecord::Base
|
9
|
+
|
10
|
+
self.table_name = LogLogins.config.events_table_name
|
11
|
+
|
12
|
+
ACTIONS = ['Success', 'Failed', 'Blocked', 'Unblocked']
|
13
|
+
|
14
|
+
belongs_to :user, :polymorphic => true
|
15
|
+
|
16
|
+
validates :action, :inclusion => {:in => ACTIONS}
|
17
|
+
|
18
|
+
# Events that have failed in the last hour (or whatever the block time might be)
|
19
|
+
scope :failed_in_block_time, -> { where(:action => 'Failed').where("created_at > ?", Time.now - LogLogins.config.block_time )}
|
20
|
+
scope :success, -> { where(:action => 'Success') }
|
21
|
+
scope :success_or_unblock, -> { where(:action => ['Success', 'Unblocked']) }
|
22
|
+
scope :failed, -> { where(:action => 'Failed') }
|
23
|
+
scope :blocked, -> { where(:action => 'Blocked') }
|
24
|
+
|
25
|
+
# Is this the first block in a series?
|
26
|
+
#
|
27
|
+
# @return [Boolean]
|
28
|
+
def first_block_in_series?
|
29
|
+
!!(self.action == 'Blocked' && (previous.nil? ||previous.action != 'Blocked'))
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return the login event that preceeded this one for the given scope
|
33
|
+
#
|
34
|
+
# @return [LogLogins::Event, nil]
|
35
|
+
def previous
|
36
|
+
similar.order(:id => :desc).where("id < ?", self.id).first
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return a scope of similar events
|
40
|
+
def similar
|
41
|
+
if self.user
|
42
|
+
self.class.where(:user => user)
|
43
|
+
elsif self.ip
|
44
|
+
self.class.where(:user => nil, :ip => ip)
|
45
|
+
else
|
46
|
+
none
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Log a new login event
|
51
|
+
#
|
52
|
+
# @param action [String] the action
|
53
|
+
# @param username [String] the username that was provided with the login attempt
|
54
|
+
# @param user [ActiveRecord::Base, nil] the user to login against or a string
|
55
|
+
# @option options [String] :ip
|
56
|
+
# @option options [String] :scope
|
57
|
+
# @option options [String] :user_agent
|
58
|
+
# @return [LogLogins::Event]
|
59
|
+
def self.log(action, username, user, ip, options = {})
|
60
|
+
event = self.new
|
61
|
+
event.user = user
|
62
|
+
event.action = action
|
63
|
+
event.username = username
|
64
|
+
event.ip = ip
|
65
|
+
event.interface = options[:interface]
|
66
|
+
event.user_agent = options[:user_agent]
|
67
|
+
if event.save
|
68
|
+
event
|
69
|
+
else
|
70
|
+
raise LogLogins::InvalidLogEventError, event.errors.full_messages.to_sentence
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Is the given user currently blocked from logging in?
|
75
|
+
#
|
76
|
+
# @param user [ActiveRecord::Base]
|
77
|
+
# @return [Boolean]
|
78
|
+
def self.user_blocked?(user)
|
79
|
+
return false unless user.is_a?(ActiveRecord::Base)
|
80
|
+
last_success = self.success_or_unblock.order(:id => :desc).select(:id).first.try(:id) || 0
|
81
|
+
self.failed_in_block_time.where(:user => user).where("id > ?", last_success).count >= LogLogins.config.attempts_before_block
|
82
|
+
end
|
83
|
+
|
84
|
+
# Is the given IP address currently blocked from logging in?
|
85
|
+
#
|
86
|
+
# @param ip [String]
|
87
|
+
# @return [Boolean]
|
88
|
+
def self.ip_blocked?(ip)
|
89
|
+
return false if ip.nil?
|
90
|
+
last_success = self.success_or_unblock.order(:id => :desc).select(:id).first.try(:id) || 0
|
91
|
+
self.failed_in_block_time.where(:user => nil, :ip => ip.to_s).where("id > ?", last_success).count >= LogLogins.config.attempts_before_block_on_ip
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
data/lib/log_logins.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'log_logins/config'
|
4
|
+
require 'log_logins/event'
|
5
|
+
require 'log_logins/user'
|
6
|
+
|
7
|
+
if defined?(Rails)
|
8
|
+
require 'log_logins/engine'
|
9
|
+
end
|
10
|
+
|
11
|
+
module LogLogins
|
12
|
+
|
13
|
+
def self.success(*args)
|
14
|
+
event = touch('Success', *args)
|
15
|
+
dispatch(:success, event)
|
16
|
+
event
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.fail(*args)
|
20
|
+
event = touch('Failed', *args)
|
21
|
+
dispatch(:fail, event)
|
22
|
+
event
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.dispatch(callback_name, event)
|
26
|
+
if callbacks = config.callbacks[callback_name.to_sym]
|
27
|
+
callbacks.each { |c| c.call(event) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.unblock_user(user)
|
32
|
+
Event.log('Unblocked', nil, user, nil)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.unblock_ip(ip)
|
36
|
+
Event.log('Unblocked', nil, nil, ip)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def self.blocked!(username, user, ip, options = {})
|
42
|
+
event = Event.log('Blocked', username, user, ip, options)
|
43
|
+
dispatch(:blocked, event)
|
44
|
+
raise LogLogins::LoginBlocked.new(event), "Login has been blocked due to too many failed logins."
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.touch(action, username, user, ip, options = {})
|
48
|
+
if Event.user_blocked?(user)
|
49
|
+
blocked!(username, user, ip, options)
|
50
|
+
end
|
51
|
+
|
52
|
+
if Event.ip_blocked?(ip)
|
53
|
+
blocked!(username, user, ip, options)
|
54
|
+
end
|
55
|
+
|
56
|
+
Event.log(action, username, user, ip, options)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
data.tar.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2#�^�h�q,O�N�[�L�����hëL��?5)QuAr�A���Dq��76^�搛�g������P&�O�X�v�W���nѦ�R��U��6�d�VȔ4���zW�Dž�x��)�6P#l�˫�
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: log_logins
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Cooke
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIEZDCCAsygAwIBAgIBATANBgkqhkiG9w0BAQsFADA8MQswCQYDVQQDDAJtZTEZ
|
14
|
+
MBcGCgmSJomT8ixkARkWCWFkYW1jb29rZTESMBAGCgmSJomT8ixkARkWAmlvMB4X
|
15
|
+
DTE4MDMwNTE3MzAwNVoXDTE5MDMwNTE3MzAwNVowPDELMAkGA1UEAwwCbWUxGTAX
|
16
|
+
BgoJkiaJk/IsZAEZFglhZGFtY29va2UxEjAQBgoJkiaJk/IsZAEZFgJpbzCCAaIw
|
17
|
+
DQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAOH6HpXwjmVYrUQxUHm25mLm9qYK
|
18
|
+
WS66Me1IfMUX3ZREZ/GzqiJZdV6itPuaaaKpbcm2A/KjgGSPOi9FZBneZ5KvbIeK
|
19
|
+
/GsixL98kxB06q9DZwJbFz7Inklxkd/S0anm+PxtWkQP1TLkMsviRcBPEAqSLON9
|
20
|
+
dCKC7+3kibhatdlsbqIQaeEhSoCUipYMi7ZyFHu5Qz+zMwc8JwHvQ4yi8cMa/QZ+
|
21
|
+
s1tN4mkp/6vWWj4G4lF3YjFYyt2txJcK5ELDtyBy7a3vbMImPy9pplFx1/M6SNpn
|
22
|
+
7Pck0LqDprRzJXsGjq3CbC0nUaudFjUPr31KwxMYq1u13aQL9YuO3GeQCQ3gvdlJ
|
23
|
+
TSd7zoGgLwrMGmXqgd392Psr29yp+WBLcvhFUJnNPDV8nlph/cqmRzoIewP1kdPq
|
24
|
+
pEIUIJQdyKJU7gmFlJ1FurarkuT0a2Rgs99WokCoXLxuPmRWQRN1sH2nHL70jgAR
|
25
|
+
UuvyXEtyALHoCn3VqBR7ZvpfDblUzfANQDhBgwIDAQABo3EwbzAJBgNVHRMEAjAA
|
26
|
+
MAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUa7gxxSE4SO2Ors4B+y3qANdMpo4wGgYD
|
27
|
+
VR0RBBMwEYEPbWVAYWRhbWNvb2tlLmlvMBoGA1UdEgQTMBGBD21lQGFkYW1jb29r
|
28
|
+
ZS5pbzANBgkqhkiG9w0BAQsFAAOCAYEAkbz/AJwBsRKwgt2BhWqgr/egf/37IS3s
|
29
|
+
utVox7feYutKyFDHXYvCjm64XUJNioG7ipbRwOOGs5bEYfwgkabcAQnxSlkdNjc4
|
30
|
+
JIgL/cF4YRg8uJG7DH+LwpydXHqr7RneDiONuiHlEN/1EZZ8tjwXypdwzhQ2/6ot
|
31
|
+
YOxdSi/mXdoDoFlIebsLyInUZjqnm7dQ9nTTUNSB+1LoOD8ARNhTIPnKCnxwZd56
|
32
|
+
giOxoHuJIOhgi6U2zicZJHv8lUj2Lc3bcirQk5eeOFRPVGQSpLLoqA7dtS7Jy4cv
|
33
|
+
3c5m+HyxSxzlrcVHMAgJYemK0uhVQD9Y6JwHKDroWDH+MPALjlScw8ui1jmNuH31
|
34
|
+
n5JOH/07C4gYcwTjJmtoRSov46Z6Gn5cc6NFkQpA185pbRLqEDKzusXvBOQlAOLh
|
35
|
+
iyQrH6PJ0xgVJNYx+DLq3eFmo2hYJkw/lVhYAK+MdajtYJbD5VvCIEHO0d5RRgV+
|
36
|
+
qnCNZoPPy0UtRmGKZTMZvVJEZiw4g0fY
|
37
|
+
-----END CERTIFICATE-----
|
38
|
+
date: 2018-03-07 00:00:00.000000000 Z
|
39
|
+
dependencies:
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: activerecord
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
description: A Rails library for logging login attempts and blocking as appropriate.
|
55
|
+
email:
|
56
|
+
- me@adamcooke.io
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- db/migrate/20180307100300_create_log_logins_events_table.rb
|
62
|
+
- lib/log_logins.rb
|
63
|
+
- lib/log_logins/config.rb
|
64
|
+
- lib/log_logins/engine.rb
|
65
|
+
- lib/log_logins/error.rb
|
66
|
+
- lib/log_logins/event.rb
|
67
|
+
- lib/log_logins/user.rb
|
68
|
+
- lib/log_logins/version.rb
|
69
|
+
homepage: https://github.com/adamcooke/log_logins
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
metadata: {}
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 2.7.4
|
90
|
+
signing_key:
|
91
|
+
specification_version: 4
|
92
|
+
summary: A Rails library for logging login attempts and blocking as appropriate.
|
93
|
+
test_files: []
|
metadata.gz.sig
ADDED
Binary file
|