log_logins 1.0.0
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 +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
|