users: track logins, signups, and other user events.
Add tracking of certain important user actions. These events include: * Logins * Logouts * Failed login attempts * Account creations * Account deletions * Password reset requests * Password changes * Email address changes This is similar to the mod actions log, except for account activity related to a single user. The information tracked includes the user, the event type (login, logout, etc), the timestamp, the user's IP address, IP geolocation information, the user's browser user agent, and the user's session ID from their session cookie. This information is visible to mods only. This is done with three models. The UserEvent model tracks the event type (login, logout, password change, etc) and the user. The UserEvent is tied to a UserSession, which contains the user's IP address and browser metadata. Finally, the IpGeolocation model contains the geolocation information for IPs, including the city, country, ISP, and whether the IP is a proxy. This tracking will be used for a few purposes: * Letting users view their account history, to detect things like logins from unrecognized IPs, failed logins attempts, password changes, etc. * Rate limiting failed login attempts. * Detecting sockpuppet accounts using their login history. * Detecting unauthorized account sharing.
This commit is contained in:
@@ -26,6 +26,7 @@ class EmailsController < ApplicationController
|
|||||||
@user = authorize User.find(params[:user_id]), policy_class: EmailAddressPolicy
|
@user = authorize User.find(params[:user_id]), policy_class: EmailAddressPolicy
|
||||||
|
|
||||||
if @user.authenticate_password(params[:user][:password])
|
if @user.authenticate_password(params[:user][:password])
|
||||||
|
UserEvent.build_from_request(@user, :email_change, request)
|
||||||
@user.update(email_address_attributes: { address: params[:user][:email] })
|
@user.update(email_address_attributes: { address: params[:user][:email] })
|
||||||
else
|
else
|
||||||
@user.errors.add(:base, "Password was incorrect")
|
@user.errors.add(:base, "Password was incorrect")
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class IpAddressesController < ApplicationController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
@ip_address = authorize IpAddress.new(ip_addr: params[:id])
|
@ip_address = authorize IpAddress.new(ip_addr: params[:id])
|
||||||
@ip_info = @ip_address.lookup.info
|
@ip_info = @ip_address.lookup.response
|
||||||
respond_with(@ip_info)
|
respond_with(@ip_info)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
9
app/controllers/ip_geolocations_controller.rb
Normal file
9
app/controllers/ip_geolocations_controller.rb
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
class IpGeolocationsController < ApplicationController
|
||||||
|
respond_to :html, :json, :xml
|
||||||
|
|
||||||
|
def index
|
||||||
|
@ip_geolocations = authorize IpGeolocation.visible(CurrentUser.user).paginated_search(params, count_pages: true)
|
||||||
|
|
||||||
|
respond_with(@ip_geolocations)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -7,7 +7,7 @@ module Maintenance
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
deletion = UserDeletion.new(CurrentUser.user, params.dig(:user, :password))
|
deletion = UserDeletion.new(CurrentUser.user, params.dig(:user, :password), request)
|
||||||
deletion.delete!
|
deletion.delete!
|
||||||
|
|
||||||
if deletion.errors.none?
|
if deletion.errors.none?
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class PasswordResetsController < ApplicationController
|
|||||||
redirect_to password_reset_path
|
redirect_to password_reset_path
|
||||||
elsif @user.can_receive_email?(require_verification: false)
|
elsif @user.can_receive_email?(require_verification: false)
|
||||||
UserMailer.password_reset(@user).deliver_later
|
UserMailer.password_reset(@user).deliver_later
|
||||||
|
UserEvent.create_from_request!(@user, :password_reset, request)
|
||||||
flash[:notice] = "Password reset email sent. Check your email"
|
flash[:notice] = "Password reset email sent. Check your email"
|
||||||
respond_with(@user, location: new_session_path)
|
respond_with(@user, location: new_session_path)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class PasswordsController < ApplicationController
|
|||||||
@user = authorize User.find(params[:user_id]), policy_class: PasswordPolicy
|
@user = authorize User.find(params[:user_id]), policy_class: PasswordPolicy
|
||||||
|
|
||||||
if @user.authenticate_password(params[:user][:old_password]) || @user.authenticate_login_key(params[:user][:signed_user_id]) || CurrentUser.user.is_owner?
|
if @user.authenticate_password(params[:user][:old_password]) || @user.authenticate_login_key(params[:user][:signed_user_id]) || CurrentUser.user.is_owner?
|
||||||
|
UserEvent.build_from_request(@user, :password_change, request)
|
||||||
@user.update(password: params[:user][:password], password_confirmation: params[:user][:password_confirmation])
|
@user.update(password: params[:user][:password], password_confirmation: params[:user][:password_confirmation])
|
||||||
else
|
else
|
||||||
@user.errors.add(:base, "Incorrect password")
|
@user.errors.add(:base, "Incorrect password")
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class SessionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
session.delete(:user_id)
|
SessionLoader.new(request).logout
|
||||||
redirect_to(posts_path, :notice => "You are now logged out")
|
redirect_to(posts_path, :notice => "You are now logged out")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
10
app/controllers/user_events_controller.rb
Normal file
10
app/controllers/user_events_controller.rb
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
class UserEventsController < ApplicationController
|
||||||
|
respond_to :html, :json, :xml
|
||||||
|
|
||||||
|
def index
|
||||||
|
@user_events = authorize UserEvent.visible(CurrentUser.user).paginated_search(params, count_pages: true)
|
||||||
|
@user_events = @user_events.includes(:user, user_session: [:ip_geolocation]) if request.format.html?
|
||||||
|
|
||||||
|
respond_with(@user_events)
|
||||||
|
end
|
||||||
|
end
|
||||||
9
app/controllers/user_sessions_controller.rb
Normal file
9
app/controllers/user_sessions_controller.rb
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
class UserSessionsController < ApplicationController
|
||||||
|
respond_to :html, :json, :xml
|
||||||
|
|
||||||
|
def index
|
||||||
|
@user_sessions = authorize UserSession.visible(CurrentUser.user).paginated_search(params, count_pages: true)
|
||||||
|
|
||||||
|
respond_with(@user_sessions)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -70,6 +70,8 @@ class UsersController < ApplicationController
|
|||||||
password_confirmation: params[:user][:password_confirmation]
|
password_confirmation: params[:user][:password_confirmation]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
UserEvent.build_from_request(@user, :user_creation, request)
|
||||||
|
|
||||||
if params[:user][:email].present?
|
if params[:user][:email].present?
|
||||||
@user.email_address = EmailAddress.new(address: params[:user][:email])
|
@user.email_address = EmailAddress.new(address: params[:user][:email])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ module Searchable
|
|||||||
search_text_attribute(name, params)
|
search_text_attribute(name, params)
|
||||||
when :boolean
|
when :boolean
|
||||||
search_boolean_attribute(name, params)
|
search_boolean_attribute(name, params)
|
||||||
when :integer, :datetime
|
when :integer, :float, :datetime
|
||||||
search_numeric_attribute(name, params)
|
search_numeric_attribute(name, params)
|
||||||
when :inet
|
when :inet
|
||||||
search_inet_attribute(name, params)
|
search_inet_attribute(name, params)
|
||||||
|
|||||||
@@ -9,23 +9,51 @@ class IpLookup
|
|||||||
Danbooru.config.ip_registry_api_key.present?
|
Danbooru.config.ip_registry_api_key.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(ip_addr, api_key: Danbooru.config.ip_registry_api_key, cache_duration: 1.day)
|
def initialize(ip_addr, api_key: Danbooru.config.ip_registry_api_key, cache_duration: 3.days)
|
||||||
@ip_addr = ip_addr
|
@ip_addr = IPAddress.parse(ip_addr.to_s)
|
||||||
@api_key = api_key
|
@api_key = api_key
|
||||||
@cache_duration = cache_duration
|
@cache_duration = cache_duration
|
||||||
end
|
end
|
||||||
|
|
||||||
def info
|
def ip_info
|
||||||
|
return {} if response.blank?
|
||||||
|
|
||||||
|
{
|
||||||
|
ip_addr: ip_addr.to_s,
|
||||||
|
network: response.dig(:connection, :route),
|
||||||
|
asn: response.dig(:connection, :asn),
|
||||||
|
is_proxy: is_proxy?,
|
||||||
|
latitude: response.dig(:location, :latitude),
|
||||||
|
longitude: response.dig(:location, :longitude),
|
||||||
|
organization: response.dig(:connection, :organization),
|
||||||
|
time_zone: response.dig(:time_zone, :id),
|
||||||
|
continent: response.dig(:location, :continent, :code),
|
||||||
|
country: response.dig(:location, :country, :code),
|
||||||
|
region: response.dig(:location, :region, :code),
|
||||||
|
city: response.dig(:location, :city),
|
||||||
|
carrier: response.dig(:carrier, :name),
|
||||||
|
}.with_indifferent_access
|
||||||
|
end
|
||||||
|
|
||||||
|
def response
|
||||||
return {} if api_key.blank?
|
return {} if api_key.blank?
|
||||||
response = Danbooru::Http.cache(cache_duration).get("https://api.ipregistry.co/#{ip_addr}?key=#{api_key}")
|
response = Danbooru::Http.cache(cache_duration).get("https://api.ipregistry.co/#{ip_addr.to_s}?key=#{api_key}")
|
||||||
return {} if response.status != 200
|
return {} if response.status != 200
|
||||||
json = response.parse.deep_symbolize_keys.with_indifferent_access
|
json = response.parse.deep_symbolize_keys.with_indifferent_access
|
||||||
json
|
json
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_proxy?
|
def is_local?
|
||||||
info[:security].present? && info[:security].values.any?
|
if ip_addr.ipv4?
|
||||||
|
ip_addr.loopback? || ip_addr.link_local? || ip_addr.private?
|
||||||
|
elsif ip_addr.ipv6?
|
||||||
|
ip_addr.loopback? || ip_addr.link_local? || ip_addr.unique_local?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
memoize :info, :is_proxy?
|
def is_proxy?
|
||||||
|
response[:security].present? && response[:security].values.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
memoize :response, :is_proxy?
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,12 +10,28 @@ class SessionLoader
|
|||||||
end
|
end
|
||||||
|
|
||||||
def login(name, password)
|
def login(name, password)
|
||||||
user = User.find_by_name(name)&.authenticate_password(password)
|
user = User.find_by_name(name)
|
||||||
return nil unless user
|
|
||||||
|
|
||||||
session[:user_id] = user.id
|
if user.present? && user.authenticate_password(password)
|
||||||
user.update_column(:last_ip_addr, request.remote_ip)
|
session[:user_id] = user.id
|
||||||
user
|
|
||||||
|
UserEvent.build_from_request(user, :login, request)
|
||||||
|
user.last_logged_in_at = Time.now
|
||||||
|
user.last_ip_addr = request.remote_ip
|
||||||
|
user.save!
|
||||||
|
|
||||||
|
user
|
||||||
|
elsif user.nil?
|
||||||
|
nil # username incorrect
|
||||||
|
else
|
||||||
|
UserEvent.create_from_request!(user, :failed_login, request)
|
||||||
|
nil # password incorrect
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def logout
|
||||||
|
session.delete(:user_id)
|
||||||
|
UserEvent.create_from_request!(CurrentUser.user, :logout, request)
|
||||||
end
|
end
|
||||||
|
|
||||||
def load
|
def load
|
||||||
@@ -76,6 +92,7 @@ class SessionLoader
|
|||||||
CurrentUser.user = user
|
CurrentUser.user = user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# XXX use rails 6.1 signed ids (https://github.com/rails/rails/blob/6-1-stable/activerecord/CHANGELOG.md)
|
||||||
def load_param_user(signed_user_id)
|
def load_param_user(signed_user_id)
|
||||||
session[:user_id] = Danbooru::MessageVerifier.new(:login).verify(signed_user_id)
|
session[:user_id] = Danbooru::MessageVerifier.new(:login).verify(signed_user_id)
|
||||||
load_session_user
|
load_session_user
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
class UserDeletion
|
class UserDeletion
|
||||||
include ActiveModel::Validations
|
include ActiveModel::Validations
|
||||||
|
|
||||||
attr_reader :user, :password
|
attr_reader :user, :password, :request
|
||||||
|
|
||||||
validate :validate_deletion
|
validate :validate_deletion
|
||||||
|
|
||||||
def initialize(user, password)
|
def initialize(user, password, request)
|
||||||
@user = user
|
@user = user
|
||||||
@password = password
|
@password = password
|
||||||
|
@request = request
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete!
|
def delete!
|
||||||
return false if invalid?
|
return false if invalid?
|
||||||
|
|
||||||
clear_user_settings
|
clear_user_settings
|
||||||
remove_favorites
|
remove_favorites
|
||||||
clear_saved_searches
|
clear_saved_searches
|
||||||
rename
|
rename
|
||||||
reset_password
|
reset_password
|
||||||
create_mod_action
|
create_mod_action
|
||||||
|
create_user_event
|
||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -27,6 +30,10 @@ class UserDeletion
|
|||||||
ModAction.log("user ##{user.id} deleted", :user_delete)
|
ModAction.log("user ##{user.id} deleted", :user_delete)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_user_event
|
||||||
|
UserEvent.create_from_request!(user, :user_deletion, request)
|
||||||
|
end
|
||||||
|
|
||||||
def clear_saved_searches
|
def clear_saved_searches
|
||||||
SavedSearch.where(user_id: user.id).destroy_all
|
SavedSearch.where(user_id: user.id).destroy_all
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -33,11 +33,7 @@ class UserVerifier
|
|||||||
end
|
end
|
||||||
|
|
||||||
def is_local_ip?
|
def is_local_ip?
|
||||||
if ip_address.ipv4?
|
IpLookup.new(ip_address).is_local?
|
||||||
ip_address.loopback? || ip_address.link_local? || ip_address.private?
|
|
||||||
elsif ip_address.ipv6?
|
|
||||||
ip_address.loopback? || ip_address.link_local? || ip_address.unique_local?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_logged_in?
|
def is_logged_in?
|
||||||
|
|||||||
29
app/models/ip_geolocation.rb
Normal file
29
app/models/ip_geolocation.rb
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# An IpGeolocation contains metadata associated with an IP address, primarily geolocation data.
|
||||||
|
|
||||||
|
class IpGeolocation < ApplicationRecord
|
||||||
|
has_many :user_sessions, foreign_key: :ip_addr, primary_key: :ip_addr
|
||||||
|
|
||||||
|
def self.visible(user)
|
||||||
|
if user.is_moderator?
|
||||||
|
all
|
||||||
|
else
|
||||||
|
none
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.search(params)
|
||||||
|
q = search_attributes(params, :id, :created_at, :updated_at, :ip_addr, :network, :asn, :is_proxy, :latitude, :longitude, :organization, :time_zone, :continent, :country, :region, :city, :carrier)
|
||||||
|
q = q.apply_default_order(params)
|
||||||
|
q
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create_or_update!(ip_addr)
|
||||||
|
ip_lookup = IpLookup.new(ip_addr)
|
||||||
|
return nil if ip_lookup.is_local?
|
||||||
|
return nil if ip_lookup.ip_info.blank?
|
||||||
|
|
||||||
|
ip_geolocation = IpGeolocation.create_with(**ip_lookup.ip_info).create_or_find_by!(ip_addr: ip_addr)
|
||||||
|
ip_geolocation.update!(**ip_lookup.ip_info)
|
||||||
|
ip_geolocation
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -102,6 +102,7 @@ class User < ApplicationRecord
|
|||||||
has_many :bans, -> {order("bans.id desc")}
|
has_many :bans, -> {order("bans.id desc")}
|
||||||
has_many :received_upgrades, class_name: "UserUpgrade", foreign_key: :recipient_id, dependent: :destroy
|
has_many :received_upgrades, class_name: "UserUpgrade", foreign_key: :recipient_id, dependent: :destroy
|
||||||
has_many :purchased_upgrades, class_name: "UserUpgrade", foreign_key: :purchaser_id, dependent: :destroy
|
has_many :purchased_upgrades, class_name: "UserUpgrade", foreign_key: :purchaser_id, dependent: :destroy
|
||||||
|
has_many :user_events, dependent: :destroy
|
||||||
has_one :recent_ban, -> {order("bans.id desc")}, :class_name => "Ban"
|
has_one :recent_ban, -> {order("bans.id desc")}, :class_name => "Ban"
|
||||||
|
|
||||||
has_one :api_key
|
has_one :api_key
|
||||||
|
|||||||
54
app/models/user_event.rb
Normal file
54
app/models/user_event.rb
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# A UserEvent is used to track important events related to a user's account,
|
||||||
|
# such as signups, logins, password changes, etc. A UserEvent is associated
|
||||||
|
# with a UserSession, which contains the IP and browser information associated
|
||||||
|
# with the event.
|
||||||
|
|
||||||
|
class UserEvent < ApplicationRecord
|
||||||
|
belongs_to :user
|
||||||
|
belongs_to :user_session
|
||||||
|
|
||||||
|
enum category: {
|
||||||
|
login: 0,
|
||||||
|
failed_login: 50,
|
||||||
|
logout: 100,
|
||||||
|
user_creation: 200,
|
||||||
|
user_deletion: 300,
|
||||||
|
password_reset: 400,
|
||||||
|
password_change: 500,
|
||||||
|
email_change: 600,
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate :session_id, :ip_addr, :ip_geolocation, to: :user_session
|
||||||
|
delegate :country, :city, :is_proxy?, to: :ip_geolocation, allow_nil: true
|
||||||
|
|
||||||
|
def self.visible(user)
|
||||||
|
if user.is_moderator?
|
||||||
|
all
|
||||||
|
else
|
||||||
|
where(user: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.search(params)
|
||||||
|
q = search_attributes(params, :id, :created_at, :updated_at, :category, :user, :user_session)
|
||||||
|
q = q.apply_default_order(params)
|
||||||
|
q
|
||||||
|
end
|
||||||
|
|
||||||
|
concerning :ConstructorMethods do
|
||||||
|
class_methods do
|
||||||
|
# Build an event but don't save it yet. The caller is expected to update the user, which will save the event.
|
||||||
|
def build_from_request(user, category, request)
|
||||||
|
ip_addr = request.remote_ip
|
||||||
|
IpGeolocation.create_or_update!(ip_addr)
|
||||||
|
user_session = UserSession.new(session_id: request.session[:session_id], ip_addr: ip_addr, user_agent: request.user_agent)
|
||||||
|
|
||||||
|
user.user_events.build(user: user, category: category, user_session: user_session)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_from_request!(...)
|
||||||
|
build_from_request(...).save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
21
app/models/user_session.rb
Normal file
21
app/models/user_session.rb
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# A UserSession contains browser and IP metadata associated with a UserEvent. This
|
||||||
|
# includes the user's session ID from their session cookie, their IP address,
|
||||||
|
# and their browser user agent. This is used to track logins and other events.
|
||||||
|
|
||||||
|
class UserSession < ApplicationRecord
|
||||||
|
belongs_to :ip_geolocation, foreign_key: :ip_addr, primary_key: :ip_addr, optional: true
|
||||||
|
|
||||||
|
def self.visible(user)
|
||||||
|
if user.is_moderator?
|
||||||
|
all
|
||||||
|
else
|
||||||
|
none
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.search(params)
|
||||||
|
q = search_attributes(params, :id, :created_at, :updated_at, :session_id, :user_agent, :ip_addr, :ip_geolocation)
|
||||||
|
q = q.apply_default_order(params)
|
||||||
|
q
|
||||||
|
end
|
||||||
|
end
|
||||||
5
app/policies/ip_geolocation_policy.rb
Normal file
5
app/policies/ip_geolocation_policy.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
class IpGeolocationPolicy < ApplicationPolicy
|
||||||
|
def index?
|
||||||
|
user.is_moderator?
|
||||||
|
end
|
||||||
|
end
|
||||||
5
app/policies/user_event_policy.rb
Normal file
5
app/policies/user_event_policy.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
class UserEventPolicy < ApplicationPolicy
|
||||||
|
def index?
|
||||||
|
user.is_moderator?
|
||||||
|
end
|
||||||
|
end
|
||||||
5
app/policies/user_session_policy.rb
Normal file
5
app/policies/user_session_policy.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
class UserSessionPolicy < ApplicationPolicy
|
||||||
|
def index?
|
||||||
|
user.is_moderator?
|
||||||
|
end
|
||||||
|
end
|
||||||
43
app/views/ip_geolocations/index.html.erb
Normal file
43
app/views/ip_geolocations/index.html.erb
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<div id="c-ip-geolocations">
|
||||||
|
<div id="a-index">
|
||||||
|
<%= search_form_for(ip_geolocations_path) do |f| %>
|
||||||
|
<%= f.input :ip_addr, label: "IP Address", input_html: { value: params[:search][:ip_addr] } %>
|
||||||
|
<%= f.input :asn, input_html: { value: params[:search][:asn] } %>
|
||||||
|
<%= f.input :continent, input_html: { value: params[:search][:continent] } %>
|
||||||
|
<%= f.input :country, as: :string, input_html: { value: params[:search][:country] } %>
|
||||||
|
<%= f.input :region, input_html: { value: params[:search][:region] } %>
|
||||||
|
<%= f.input :city, input_html: { value: params[:search][:city] } %>
|
||||||
|
<%= f.input :is_proxy, label: "Proxy?", as: :select, include_blank: true, selected: params[:search][:is_proxy] %>
|
||||||
|
<%= f.submit "Search" %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= table_for @ip_geolocations, class: "striped autofit" do |t| %>
|
||||||
|
<% t.column "IP Address" do |ip_geolocation| %>
|
||||||
|
<%= ip_geolocation.ip_addr %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% t.column :network %>
|
||||||
|
<% t.column :asn %>
|
||||||
|
<% t.column :organization %>
|
||||||
|
<% t.column :is_proxy %>
|
||||||
|
<% t.column :continent %>
|
||||||
|
<% t.column :country %>
|
||||||
|
<% t.column :region %>
|
||||||
|
<% t.column :city %>
|
||||||
|
|
||||||
|
<% t.column "Updated" do |ip_geolocation| %>
|
||||||
|
<%= time_ago_in_words_tagged(ip_geolocation.updated_at) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% t.column "Created" do |ip_geolocation| %>
|
||||||
|
<%= time_ago_in_words_tagged(ip_geolocation.created_at) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% t.column column: "control" do |ip_geolocation| %>
|
||||||
|
<%= external_link_to "https://ipinfo.io/#{ip_geolocation.ip_addr}", "IP Info" %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= numbered_paginator(@ip_geolocations) %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
66
app/views/user_events/index.html.erb
Normal file
66
app/views/user_events/index.html.erb
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<div id="c-user-events">
|
||||||
|
<div id="a-index">
|
||||||
|
<%= search_form_for(user_events_path) do |f| %>
|
||||||
|
<%= f.input :user_name, label: "User", input_html: { value: params[:search][:user_name], "data-autocomplete": "user" } %>
|
||||||
|
<%= f.simple_fields_for :user_session do |f1| %>
|
||||||
|
<%= f1.input :ip_addr, label: "IP Address", input_html: { value: params.dig(:search, :user_session, :ip_addr) } %>
|
||||||
|
<%= f1.input :session_id, label: "Session", input_html: { value: params.dig(:search, :user_session, :session_id) } %>
|
||||||
|
<%= f1.simple_fields_for :ip_geolocation do |f2| %>
|
||||||
|
<%= f2.input :country, as: :string, input_html: { value: params.dig(:search, :user_session, :ip_geolocation, :country) } %>
|
||||||
|
<%= f2.input :city, input_html: { value: params.dig(:search, :user_session, :ip_geolocation, :city) } %>
|
||||||
|
<%= f2.input :is_proxy, label: "Proxy?", as: :select, include_blank: true, selected: params.dig(:search, :user_session, :ip_geolocation, :is_proxy) %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<%= f.input :category, collection: UserEvent.categories.transform_keys(&:humanize), include_blank: true, selected: params[:search][:category] %>
|
||||||
|
<%= f.submit "Search" %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= table_for @user_events, class: "striped autofit" do |t| %>
|
||||||
|
<% t.column "User" do |user_event| %>
|
||||||
|
<%= link_to_user user_event.user %>
|
||||||
|
<%= link_to "»", user_events_path(search: { user_name: user_event.user.name }) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% t.column :category do |user_event| %>
|
||||||
|
<%= link_to user_event.category.humanize, user_events_path(search: { category: UserEvent.categories[user_event.category] }) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% t.column "Session" do |user_event| %>
|
||||||
|
<%= link_to user_event.session_id, user_events_path(search: { user_session: { session_id: user_event.session_id }}) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% t.column "IP Address" do |user_event| %>
|
||||||
|
<%= link_to user_event.ip_addr, user_events_path(search: { user_session: { ip_addr: user_event.ip_addr }}) %>
|
||||||
|
<%= link_to "»", ip_geolocations_path(search: { ip_addr: user_event.ip_addr }) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% t.column "Country" do |user_event| %>
|
||||||
|
<% if user_event.country.present? %>
|
||||||
|
<%= link_to user_event.country, user_events_path(search: { user_session: { ip_geolocation: { country: user_event.country }}}) %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% t.column "City" do |user_event| %>
|
||||||
|
<% if user_event.city.present? %>
|
||||||
|
<%= link_to user_event.city, user_events_path(search: { user_session: { ip_geolocation: { city: user_event.city }}}) %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% t.column "Proxy?" do |user_event| %>
|
||||||
|
<% if user_event.is_proxy?.present? %>
|
||||||
|
<%= link_to user_event.is_proxy? ? "Yes" : "No", user_events_path(search: { user_session: { ip_geolocation: { is_proxy: user_event.is_proxy? }}}) %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% t.column "Created" do |user_event| %>
|
||||||
|
<%= time_ago_in_words_tagged(user_event.created_at) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% t.column column: "control" do |user_event| %>
|
||||||
|
<%= external_link_to "https://ipinfo.io/#{user_event.ip_addr}", "IP Info" %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= numbered_paginator(@user_events) %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
31
app/views/user_sessions/index.html.erb
Normal file
31
app/views/user_sessions/index.html.erb
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<div id="c-user-sessions">
|
||||||
|
<div id="a-index">
|
||||||
|
<%= search_form_for(user_sessions_path) do |f| %>
|
||||||
|
<%= f.input :session_id, label: "Session", input_html: { value: params[:search][:session_id] } %>
|
||||||
|
<%= f.input :ip_addr, label: "IP Address", input_html: { value: params[:search][:ip_addr] } %>
|
||||||
|
<%= f.submit "Search" %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= table_for @user_sessions, class: "striped autofit" do |t| %>
|
||||||
|
<% t.column "Session" do |user_session| %>
|
||||||
|
<%= link_to user_session.session_id, user_sessions_path(search: { session_id: user_session.session_id }) %>
|
||||||
|
<%= link_to "»", user_events_path(search: { user_session: { session_id: user_session.session_id }}) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% t.column "IP Address" do |user_session| %>
|
||||||
|
<%= link_to user_session.ip_addr, user_sessions_path(search: { ip_addr: user_session.ip_addr }) %>
|
||||||
|
<%= link_to "»", user_events_path(search: { user_session: { ip_addr: user_session.ip_addr }}) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% t.column "Browser" do |user_session| %>
|
||||||
|
<%= user_session.user_agent %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% t.column "Created" do |user_session| %>
|
||||||
|
<%= time_ago_in_words_tagged(user_session.created_at) %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= numbered_paginator(@user_sessions) %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -140,6 +140,7 @@ Rails.application.routes.draw do
|
|||||||
resources :forum_topic_visits, only: [:index]
|
resources :forum_topic_visits, only: [:index]
|
||||||
resources :ip_bans, only: [:index, :new, :create, :update]
|
resources :ip_bans, only: [:index, :new, :create, :update]
|
||||||
resources :ip_addresses, only: [:show, :index], id: /.+?(?=\.json|\.xml|\.html)|.+/
|
resources :ip_addresses, only: [:show, :index], id: /.+?(?=\.json|\.xml|\.html)|.+/
|
||||||
|
resources :ip_geolocations, only: [:index]
|
||||||
resource :iqdb_queries, :only => [:show, :create] do
|
resource :iqdb_queries, :only => [:show, :create] do
|
||||||
collection do
|
collection do
|
||||||
get :preview
|
get :preview
|
||||||
@@ -260,7 +261,9 @@ Rails.application.routes.draw do
|
|||||||
get :payment, on: :member
|
get :payment, on: :member
|
||||||
put :refund, on: :member
|
put :refund, on: :member
|
||||||
end
|
end
|
||||||
|
resources :user_events, only: [:index]
|
||||||
resources :user_feedbacks, except: [:destroy]
|
resources :user_feedbacks, except: [:destroy]
|
||||||
|
resources :user_sessions, only: [:index]
|
||||||
resources :user_name_change_requests, only: [:new, :create, :show, :index]
|
resources :user_name_change_requests, only: [:new, :create, :show, :index]
|
||||||
resources :webhooks do
|
resources :webhooks do
|
||||||
post :receive, on: :collection
|
post :receive, on: :collection
|
||||||
|
|||||||
21
db/migrate/20210108030722_create_ip_geolocations.rb
Normal file
21
db/migrate/20210108030722_create_ip_geolocations.rb
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
class CreateIpGeolocations < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
create_table :ip_geolocations do |t|
|
||||||
|
t.timestamps null: false, index: true
|
||||||
|
|
||||||
|
t.inet :ip_addr, null: false, index: { unique: true }
|
||||||
|
t.inet :network, index: true
|
||||||
|
t.integer :asn, index: true
|
||||||
|
t.boolean :is_proxy, null: false, index: true
|
||||||
|
t.float :latitude, index: true
|
||||||
|
t.float :longitude, index: true
|
||||||
|
t.string :organization, index: true
|
||||||
|
t.string :time_zone, index: true
|
||||||
|
t.string :continent, index: true
|
||||||
|
t.string :country, index: true
|
||||||
|
t.string :region, index: true
|
||||||
|
t.string :city, index: true
|
||||||
|
t.string :carrier, index: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
10
db/migrate/20210108030723_create_user_sessions.rb
Normal file
10
db/migrate/20210108030723_create_user_sessions.rb
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
class CreateUserSessions < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
create_table :user_sessions do |t|
|
||||||
|
t.timestamps null: false, index: true
|
||||||
|
t.inet :ip_addr, null: false, index: true
|
||||||
|
t.string :session_id, null: false, index: true
|
||||||
|
t.string :user_agent
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
10
db/migrate/20210108030724_create_user_events.rb
Normal file
10
db/migrate/20210108030724_create_user_events.rb
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
class CreateUserEvents < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
create_table :user_events do |t|
|
||||||
|
t.timestamps null: false, index: true
|
||||||
|
t.references :user, null: false, index: true
|
||||||
|
t.references :user_session, null: false, index: true
|
||||||
|
t.integer :category, null: false, index: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
325
db/structure.sql
325
db/structure.sql
@@ -2364,6 +2364,49 @@ CREATE SEQUENCE public.ip_bans_id_seq
|
|||||||
ALTER SEQUENCE public.ip_bans_id_seq OWNED BY public.ip_bans.id;
|
ALTER SEQUENCE public.ip_bans_id_seq OWNED BY public.ip_bans.id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: ip_geolocations; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.ip_geolocations (
|
||||||
|
id bigint NOT NULL,
|
||||||
|
created_at timestamp(6) without time zone NOT NULL,
|
||||||
|
updated_at timestamp(6) without time zone NOT NULL,
|
||||||
|
ip_addr inet NOT NULL,
|
||||||
|
network inet,
|
||||||
|
asn integer,
|
||||||
|
is_proxy boolean NOT NULL,
|
||||||
|
latitude double precision,
|
||||||
|
longitude double precision,
|
||||||
|
organization character varying,
|
||||||
|
time_zone character varying,
|
||||||
|
continent character varying,
|
||||||
|
country character varying,
|
||||||
|
region character varying,
|
||||||
|
city character varying,
|
||||||
|
carrier character varying
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: ip_geolocations_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE SEQUENCE public.ip_geolocations_id_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
NO MAXVALUE
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: ip_geolocations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER SEQUENCE public.ip_geolocations_id_seq OWNED BY public.ip_geolocations.id;
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: mod_actions; Type: TABLE; Schema: public; Owner: -
|
-- Name: mod_actions; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -3038,6 +3081,39 @@ CREATE SEQUENCE public.uploads_id_seq
|
|||||||
ALTER SEQUENCE public.uploads_id_seq OWNED BY public.uploads.id;
|
ALTER SEQUENCE public.uploads_id_seq OWNED BY public.uploads.id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_events; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.user_events (
|
||||||
|
id bigint NOT NULL,
|
||||||
|
created_at timestamp(6) without time zone NOT NULL,
|
||||||
|
updated_at timestamp(6) without time zone NOT NULL,
|
||||||
|
user_id bigint NOT NULL,
|
||||||
|
user_session_id bigint NOT NULL,
|
||||||
|
category integer NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_events_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE SEQUENCE public.user_events_id_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
NO MAXVALUE
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_events_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER SEQUENCE public.user_events_id_seq OWNED BY public.user_events.id;
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: user_feedback; Type: TABLE; Schema: public; Owner: -
|
-- Name: user_feedback; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -3106,6 +3182,39 @@ CREATE SEQUENCE public.user_name_change_requests_id_seq
|
|||||||
ALTER SEQUENCE public.user_name_change_requests_id_seq OWNED BY public.user_name_change_requests.id;
|
ALTER SEQUENCE public.user_name_change_requests_id_seq OWNED BY public.user_name_change_requests.id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_sessions; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.user_sessions (
|
||||||
|
id bigint NOT NULL,
|
||||||
|
created_at timestamp(6) without time zone NOT NULL,
|
||||||
|
updated_at timestamp(6) without time zone NOT NULL,
|
||||||
|
ip_addr inet NOT NULL,
|
||||||
|
session_id character varying NOT NULL,
|
||||||
|
user_agent character varying
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_sessions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE SEQUENCE public.user_sessions_id_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
NO MAXVALUE
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_sessions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER SEQUENCE public.user_sessions_id_seq OWNED BY public.user_sessions.id;
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: user_upgrades; Type: TABLE; Schema: public; Owner: -
|
-- Name: user_upgrades; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -4062,6 +4171,13 @@ ALTER TABLE ONLY public.forum_topics ALTER COLUMN id SET DEFAULT nextval('public
|
|||||||
ALTER TABLE ONLY public.ip_bans ALTER COLUMN id SET DEFAULT nextval('public.ip_bans_id_seq'::regclass);
|
ALTER TABLE ONLY public.ip_bans ALTER COLUMN id SET DEFAULT nextval('public.ip_bans_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: ip_geolocations id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.ip_geolocations ALTER COLUMN id SET DEFAULT nextval('public.ip_geolocations_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: mod_actions id; Type: DEFAULT; Schema: public; Owner: -
|
-- Name: mod_actions id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -4195,6 +4311,13 @@ ALTER TABLE ONLY public.tags ALTER COLUMN id SET DEFAULT nextval('public.tags_id
|
|||||||
ALTER TABLE ONLY public.uploads ALTER COLUMN id SET DEFAULT nextval('public.uploads_id_seq'::regclass);
|
ALTER TABLE ONLY public.uploads ALTER COLUMN id SET DEFAULT nextval('public.uploads_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_events id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.user_events ALTER COLUMN id SET DEFAULT nextval('public.user_events_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: user_feedback id; Type: DEFAULT; Schema: public; Owner: -
|
-- Name: user_feedback id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -4209,6 +4332,13 @@ ALTER TABLE ONLY public.user_feedback ALTER COLUMN id SET DEFAULT nextval('publi
|
|||||||
ALTER TABLE ONLY public.user_name_change_requests ALTER COLUMN id SET DEFAULT nextval('public.user_name_change_requests_id_seq'::regclass);
|
ALTER TABLE ONLY public.user_name_change_requests ALTER COLUMN id SET DEFAULT nextval('public.user_name_change_requests_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_sessions id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.user_sessions ALTER COLUMN id SET DEFAULT nextval('public.user_sessions_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: user_upgrades id; Type: DEFAULT; Schema: public; Owner: -
|
-- Name: user_upgrades id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -4414,6 +4544,13 @@ ALTER TABLE ONLY public.ip_bans
|
|||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
|
-- Name: ip_geolocations ip_geolocations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.ip_geolocations
|
||||||
|
ADD CONSTRAINT ip_geolocations_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
-- Name: mod_actions mod_actions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
-- Name: mod_actions mod_actions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
|
||||||
@@ -4565,6 +4702,14 @@ ALTER TABLE ONLY public.uploads
|
|||||||
ADD CONSTRAINT uploads_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT uploads_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_events user_events_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.user_events
|
||||||
|
ADD CONSTRAINT user_events_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: user_feedback user_feedback_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
-- Name: user_feedback user_feedback_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -4581,6 +4726,14 @@ ALTER TABLE ONLY public.user_name_change_requests
|
|||||||
ADD CONSTRAINT user_name_change_requests_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT user_name_change_requests_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: user_sessions user_sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.user_sessions
|
||||||
|
ADD CONSTRAINT user_sessions_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: user_upgrades user_upgrades_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
-- Name: user_upgrades user_upgrades_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -6531,6 +6684,110 @@ CREATE INDEX index_ip_bans_on_is_deleted ON public.ip_bans USING btree (is_delet
|
|||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_asn; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_ip_geolocations_on_asn ON public.ip_geolocations USING btree (asn);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_carrier; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_ip_geolocations_on_carrier ON public.ip_geolocations USING btree (carrier);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_city; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_ip_geolocations_on_city ON public.ip_geolocations USING btree (city);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_continent; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_ip_geolocations_on_continent ON public.ip_geolocations USING btree (continent);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_country; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_ip_geolocations_on_country ON public.ip_geolocations USING btree (country);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_created_at; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_ip_geolocations_on_created_at ON public.ip_geolocations USING btree (created_at);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_ip_addr; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX index_ip_geolocations_on_ip_addr ON public.ip_geolocations USING btree (ip_addr);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_is_proxy; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_ip_geolocations_on_is_proxy ON public.ip_geolocations USING btree (is_proxy);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_latitude; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_ip_geolocations_on_latitude ON public.ip_geolocations USING btree (latitude);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_longitude; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_ip_geolocations_on_longitude ON public.ip_geolocations USING btree (longitude);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_network; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_ip_geolocations_on_network ON public.ip_geolocations USING btree (network);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_organization; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_ip_geolocations_on_organization ON public.ip_geolocations USING btree (organization);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_region; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_ip_geolocations_on_region ON public.ip_geolocations USING btree (region);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_time_zone; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_ip_geolocations_on_time_zone ON public.ip_geolocations USING btree (time_zone);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_ip_geolocations_on_updated_at; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_ip_geolocations_on_updated_at ON public.ip_geolocations USING btree (updated_at);
|
||||||
|
|
||||||
|
|
||||||
-- Name: index_mod_actions_on_created_at; Type: INDEX; Schema: public; Owner: -
|
-- Name: index_mod_actions_on_created_at; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
|
||||||
@@ -7062,6 +7319,41 @@ CREATE INDEX index_uploads_on_uploader_id ON public.uploads USING btree (uploade
|
|||||||
CREATE INDEX index_uploads_on_uploader_ip_addr ON public.uploads USING btree (uploader_ip_addr);
|
CREATE INDEX index_uploads_on_uploader_ip_addr ON public.uploads USING btree (uploader_ip_addr);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_user_events_on_category; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_user_events_on_category ON public.user_events USING btree (category);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_user_events_on_created_at; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_user_events_on_created_at ON public.user_events USING btree (created_at);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_user_events_on_updated_at; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_user_events_on_updated_at ON public.user_events USING btree (updated_at);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_user_events_on_user_id; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_user_events_on_user_id ON public.user_events USING btree (user_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_user_events_on_user_session_id; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_user_events_on_user_session_id ON public.user_events USING btree (user_session_id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_user_feedback_on_created_at; Type: INDEX; Schema: public; Owner: -
|
-- Name: index_user_feedback_on_created_at; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -7097,6 +7389,34 @@ CREATE INDEX index_user_name_change_requests_on_original_name ON public.user_nam
|
|||||||
CREATE INDEX index_user_name_change_requests_on_user_id ON public.user_name_change_requests USING btree (user_id);
|
CREATE INDEX index_user_name_change_requests_on_user_id ON public.user_name_change_requests USING btree (user_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_user_sessions_on_created_at; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_user_sessions_on_created_at ON public.user_sessions USING btree (created_at);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_user_sessions_on_ip_addr; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_user_sessions_on_ip_addr ON public.user_sessions USING btree (ip_addr);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_user_sessions_on_session_id; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_user_sessions_on_session_id ON public.user_sessions USING btree (session_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_user_sessions_on_updated_at; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_user_sessions_on_updated_at ON public.user_sessions USING btree (updated_at);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_user_upgrades_on_purchaser_id; Type: INDEX; Schema: public; Owner: -
|
-- Name: index_user_upgrades_on_purchaser_id; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -7526,6 +7846,9 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||||||
('20201213052805'),
|
('20201213052805'),
|
||||||
('20201219201007'),
|
('20201219201007'),
|
||||||
('20201224101208'),
|
('20201224101208'),
|
||||||
('20210106212805');
|
('20210106212805'),
|
||||||
|
('20210108030722'),
|
||||||
|
('20210108030723'),
|
||||||
|
('20210108030724');
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
5
test/factories/user_event.rb
Normal file
5
test/factories/user_event.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
FactoryBot.define do
|
||||||
|
factory(:user_event) do
|
||||||
|
user
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -89,6 +89,7 @@ class EmailsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal("abc@ogres.net", @user.reload.email_address.address)
|
assert_equal("abc@ogres.net", @user.reload.email_address.address)
|
||||||
assert_equal(false, @user.email_address.is_verified)
|
assert_equal(false, @user.email_address.is_verified)
|
||||||
assert_enqueued_email_with UserMailer, :email_change_confirmation, args: [@user], queue: "default"
|
assert_enqueued_email_with UserMailer, :email_change_confirmation, args: [@user], queue: "default"
|
||||||
|
assert_equal(true, @user.user_events.email_change.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "create a new address" do
|
should "create a new address" do
|
||||||
@@ -102,6 +103,7 @@ class EmailsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal("abc@ogres.net", @user.reload.email_address.address)
|
assert_equal("abc@ogres.net", @user.reload.email_address.address)
|
||||||
assert_equal(false, @user.reload.email_address.is_verified)
|
assert_equal(false, @user.reload.email_address.is_verified)
|
||||||
assert_enqueued_email_with UserMailer, :email_change_confirmation, args: [@user], queue: "default"
|
assert_enqueued_email_with UserMailer, :email_change_confirmation, args: [@user], queue: "default"
|
||||||
|
assert_equal(true, @user.user_events.email_change.exists?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -112,6 +114,7 @@ class EmailsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_response :success
|
assert_response :success
|
||||||
assert_equal("bob@ogres.net", @user.reload.email_address.address)
|
assert_equal("bob@ogres.net", @user.reload.email_address.address)
|
||||||
assert_no_emails
|
assert_no_emails
|
||||||
|
assert_equal(false, @user.user_events.email_change.exists?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -18,18 +18,22 @@ module Maintenance
|
|||||||
context "#destroy" do
|
context "#destroy" do
|
||||||
should "delete the user when given the correct password" do
|
should "delete the user when given the correct password" do
|
||||||
delete_auth maintenance_user_deletion_path, @user, params: { user: { password: "password" }}
|
delete_auth maintenance_user_deletion_path, @user, params: { user: { password: "password" }}
|
||||||
|
|
||||||
assert_redirected_to posts_path
|
assert_redirected_to posts_path
|
||||||
assert_equal(true, @user.reload.is_deleted?)
|
assert_equal(true, @user.reload.is_deleted?)
|
||||||
assert_equal("Your account has been deactivated", flash[:notice])
|
assert_equal("Your account has been deactivated", flash[:notice])
|
||||||
assert_nil(session[:user_id])
|
assert_nil(session[:user_id])
|
||||||
|
assert_equal(true, @user.user_events.user_deletion.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "not delete the user when given an incorrect password" do
|
should "not delete the user when given an incorrect password" do
|
||||||
delete_auth maintenance_user_deletion_path, @user, params: { user: { password: "hunter2" }}
|
delete_auth maintenance_user_deletion_path, @user, params: { user: { password: "hunter2" }}
|
||||||
|
|
||||||
assert_redirected_to maintenance_user_deletion_path
|
assert_redirected_to maintenance_user_deletion_path
|
||||||
assert_equal(false, @user.reload.is_deleted?)
|
assert_equal(false, @user.reload.is_deleted?)
|
||||||
assert_equal("Password is incorrect", flash[:notice])
|
assert_equal("Password is incorrect", flash[:notice])
|
||||||
assert_equal(@user.id, session[:user_id])
|
assert_equal(@user.id, session[:user_id])
|
||||||
|
assert_equal(false, @user.user_events.user_deletion.exists?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class PasswordResetsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
|
|
||||||
assert_redirected_to new_session_path
|
assert_redirected_to new_session_path
|
||||||
assert_enqueued_email_with UserMailer, :password_reset, args: [@user], queue: "default"
|
assert_enqueued_email_with UserMailer, :password_reset, args: [@user], queue: "default"
|
||||||
|
assert_equal(true, @user.user_events.password_reset.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "should fail if the user doesn't have a verified email address" do
|
should "should fail if the user doesn't have a verified email address" do
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class PasswordsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_redirected_to @user
|
assert_redirected_to @user
|
||||||
assert_equal(false, @user.reload.authenticate_password("12345"))
|
assert_equal(false, @user.reload.authenticate_password("12345"))
|
||||||
assert_equal(@user, @user.authenticate_password("abcde"))
|
assert_equal(@user, @user.authenticate_password("abcde"))
|
||||||
|
assert_equal(true, @user.user_events.password_change.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "update the password when given a valid login key" do
|
should "update the password when given a valid login key" do
|
||||||
@@ -29,6 +30,7 @@ class PasswordsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_redirected_to @user
|
assert_redirected_to @user
|
||||||
assert_equal(false, @user.reload.authenticate_password("12345"))
|
assert_equal(false, @user.reload.authenticate_password("12345"))
|
||||||
assert_equal(@user, @user.authenticate_password("abcde"))
|
assert_equal(@user, @user.authenticate_password("abcde"))
|
||||||
|
assert_equal(true, @user.user_events.password_change.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "allow the site owner to change the password of other users" do
|
should "allow the site owner to change the password of other users" do
|
||||||
@@ -55,6 +57,7 @@ class PasswordsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_response :success
|
assert_response :success
|
||||||
assert_equal(@user, @user.reload.authenticate_password("12345"))
|
assert_equal(@user, @user.reload.authenticate_password("12345"))
|
||||||
assert_equal(false, @user.authenticate_password("abcde"))
|
assert_equal(false, @user.authenticate_password("abcde"))
|
||||||
|
assert_equal(false, @user.user_events.password_change.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "not update the password when password confirmation fails for the new password" do
|
should "not update the password when password confirmation fails for the new password" do
|
||||||
|
|||||||
@@ -20,11 +20,20 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_redirected_to posts_path
|
assert_redirected_to posts_path
|
||||||
assert_equal(@user.id, session[:user_id])
|
assert_equal(@user.id, session[:user_id])
|
||||||
assert_not_nil(@user.reload.last_ip_addr)
|
assert_not_nil(@user.reload.last_ip_addr)
|
||||||
|
assert_equal(true, @user.user_events.login.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "not log the user in when given an incorrect password" do
|
should "not log the user in when given an incorrect password" do
|
||||||
post session_path, params: { name: @user.name, password: "wrong"}
|
post session_path, params: { name: @user.name, password: "wrong"}
|
||||||
|
|
||||||
|
assert_response 401
|
||||||
|
assert_nil(nil, session[:user_id])
|
||||||
|
assert_equal(true, @user.user_events.failed_login.exists?)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "not log the user in when given an incorrect username" do
|
||||||
|
post session_path, params: { name: "dne", password: "password" }
|
||||||
|
|
||||||
assert_response 401
|
assert_response 401
|
||||||
assert_nil(nil, session[:user_id])
|
assert_nil(nil, session[:user_id])
|
||||||
end
|
end
|
||||||
@@ -66,11 +75,18 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "destroy action" do
|
context "destroy action" do
|
||||||
should "clear the session" do
|
setup do
|
||||||
delete_auth session_path, @user
|
delete_auth session_path, @user
|
||||||
|
end
|
||||||
|
|
||||||
|
should "clear the session" do
|
||||||
assert_redirected_to posts_path
|
assert_redirected_to posts_path
|
||||||
assert_nil(session[:user_id])
|
assert_nil(session[:user_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
should "generate a logout event" do
|
||||||
|
assert_equal(true, @user.user_events.logout.exists?)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "sign_out action" do
|
context "sign_out action" do
|
||||||
|
|||||||
@@ -260,6 +260,7 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal(User.last, User.last.authenticate_password("xxxxx1"))
|
assert_equal(User.last, User.last.authenticate_password("xxxxx1"))
|
||||||
assert_nil(User.last.email_address)
|
assert_nil(User.last.email_address)
|
||||||
assert_no_enqueued_emails
|
assert_no_enqueued_emails
|
||||||
|
assert_equal(true, User.last.user_events.user_creation.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "create a user with a valid email" do
|
should "create a user with a valid email" do
|
||||||
@@ -270,6 +271,7 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal(User.last, User.last.authenticate_password("xxxxx1"))
|
assert_equal(User.last, User.last.authenticate_password("xxxxx1"))
|
||||||
assert_equal("webmaster@danbooru.donmai.us", User.last.email_address.address)
|
assert_equal("webmaster@danbooru.donmai.us", User.last.email_address.address)
|
||||||
assert_enqueued_email_with UserMailer, :welcome_user, args: [User.last], queue: "default"
|
assert_enqueued_email_with UserMailer, :welcome_user, args: [User.last], queue: "default"
|
||||||
|
assert_equal(true, User.last.user_events.user_creation.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "not create a user with an invalid email" do
|
should "not create a user with an invalid email" do
|
||||||
@@ -307,6 +309,7 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal(true, User.last.is_member?)
|
assert_equal(true, User.last.is_member?)
|
||||||
assert_equal(false, User.last.is_restricted?)
|
assert_equal(false, User.last.is_restricted?)
|
||||||
assert_equal(false, User.last.requires_verification)
|
assert_equal(false, User.last.requires_verification)
|
||||||
|
assert_equal(true, User.last.user_events.user_creation.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "mark accounts created by already logged in users as restricted" do
|
should "mark accounts created by already logged in users as restricted" do
|
||||||
@@ -318,6 +321,7 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal(false, User.last.is_member?)
|
assert_equal(false, User.last.is_member?)
|
||||||
assert_equal(true, User.last.is_restricted?)
|
assert_equal(true, User.last.is_restricted?)
|
||||||
assert_equal(true, User.last.requires_verification)
|
assert_equal(true, User.last.requires_verification)
|
||||||
|
assert_equal(true, User.last.user_events.user_creation.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "mark users signing up from proxies as restricted" do
|
should "mark users signing up from proxies as restricted" do
|
||||||
@@ -330,6 +334,7 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal(false, User.last.is_member?)
|
assert_equal(false, User.last.is_member?)
|
||||||
assert_equal(true, User.last.is_restricted?)
|
assert_equal(true, User.last.is_restricted?)
|
||||||
assert_equal(true, User.last.requires_verification)
|
assert_equal(true, User.last.requires_verification)
|
||||||
|
assert_equal(true, User.last.user_events.user_creation.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "mark users signing up from a partial banned IP as restricted" do
|
should "mark users signing up from a partial banned IP as restricted" do
|
||||||
@@ -344,6 +349,7 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal(true, User.last.requires_verification)
|
assert_equal(true, User.last.requires_verification)
|
||||||
assert_equal(1, @ip_ban.reload.hit_count)
|
assert_equal(1, @ip_ban.reload.hit_count)
|
||||||
assert(@ip_ban.last_hit_at > 1.minute.ago)
|
assert(@ip_ban.last_hit_at > 1.minute.ago)
|
||||||
|
assert_equal(true, User.last.user_events.user_creation.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "not mark users signing up from non-proxies as restricted" do
|
should "not mark users signing up from non-proxies as restricted" do
|
||||||
@@ -356,6 +362,7 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal(true, User.last.is_member?)
|
assert_equal(true, User.last.is_member?)
|
||||||
assert_equal(false, User.last.is_restricted?)
|
assert_equal(false, User.last.is_restricted?)
|
||||||
assert_equal(false, User.last.requires_verification)
|
assert_equal(false, User.last.requires_verification)
|
||||||
|
assert_equal(true, User.last.user_events.user_creation.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "mark accounts registered from an IPv4 address recently used for another account as restricted" do
|
should "mark accounts registered from an IPv4 address recently used for another account as restricted" do
|
||||||
@@ -368,6 +375,7 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal(false, User.last.is_member?)
|
assert_equal(false, User.last.is_member?)
|
||||||
assert_equal(true, User.last.is_restricted?)
|
assert_equal(true, User.last.is_restricted?)
|
||||||
assert_equal(true, User.last.requires_verification)
|
assert_equal(true, User.last.requires_verification)
|
||||||
|
assert_equal(true, User.last.user_events.user_creation.exists?)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "not mark users signing up from localhost as restricted" do
|
should "not mark users signing up from localhost as restricted" do
|
||||||
@@ -379,6 +387,7 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal(true, User.last.is_member?)
|
assert_equal(true, User.last.is_member?)
|
||||||
assert_equal(false, User.last.is_restricted?)
|
assert_equal(false, User.last.is_restricted?)
|
||||||
assert_equal(false, User.last.requires_verification)
|
assert_equal(false, User.last.requires_verification)
|
||||||
|
assert_equal(true, User.last.user_events.user_creation.exists?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
72
test/unit/ip_geolocation_test.rb
Normal file
72
test/unit/ip_geolocation_test.rb
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class IpGeolocationTest < ActiveSupport::TestCase
|
||||||
|
context "IpGeolocation: " do
|
||||||
|
context "the create_or_update! method" do
|
||||||
|
should "create a new record if the IP record doesn't already exist" do
|
||||||
|
assert_difference("IpGeolocation.count", 1) do
|
||||||
|
IpGeolocation.create_or_update!("1.1.1.1")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
should "update an existing record if the IP record already exists" do
|
||||||
|
@ip1 = IpGeolocation.create_or_update!("1.1.1.1")
|
||||||
|
@ip1.update(asn: -1)
|
||||||
|
@ip2 = IpGeolocation.create_or_update!("1.1.1.1")
|
||||||
|
|
||||||
|
assert_equal(1, IpGeolocation.count)
|
||||||
|
assert_equal(@ip1.id, @ip2.id)
|
||||||
|
assert_equal(13335, @ip1.reload.asn)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "return nothing for an invalid IP" do
|
||||||
|
assert_nil(IpGeolocation.create_or_update!("0.0.0.0"))
|
||||||
|
end
|
||||||
|
|
||||||
|
should "return nothing for a local IP" do
|
||||||
|
assert_nil(IpGeolocation.create_or_update!("127.0.0.1"))
|
||||||
|
assert_nil(IpGeolocation.create_or_update!("10.0.0.1"))
|
||||||
|
assert_nil(IpGeolocation.create_or_update!("fe80::1"))
|
||||||
|
assert_nil(IpGeolocation.create_or_update!("::1"))
|
||||||
|
end
|
||||||
|
|
||||||
|
should "work for a residential IP" do
|
||||||
|
@ip = IpGeolocation.create_or_update!("2a01:0e35:2f22:e3d0::1")
|
||||||
|
|
||||||
|
assert_equal(28, @ip.network.prefix)
|
||||||
|
assert_equal(false, @ip.is_proxy?)
|
||||||
|
assert_equal(48.75919, @ip.latitude)
|
||||||
|
assert_equal(2.16969, @ip.longitude)
|
||||||
|
assert_equal("Free SAS", @ip.organization)
|
||||||
|
assert_equal("Europe/Paris", @ip.time_zone)
|
||||||
|
assert_equal("EU", @ip.continent)
|
||||||
|
assert_equal("FR", @ip.country)
|
||||||
|
assert_equal("FR-IDF", @ip.region)
|
||||||
|
assert_equal("Jouy-en-Josas", @ip.city)
|
||||||
|
assert_nil(@ip.carrier)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "work for a mobile IP" do
|
||||||
|
@ip = IpGeolocation.create_or_update!("37.173.153.166")
|
||||||
|
assert_equal("Free Mobile", @ip.carrier)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "work for a proxy IP" do
|
||||||
|
@ip = IpGeolocation.create_or_update!("31.214.184.59")
|
||||||
|
assert_equal("Soluciones Corporativas IP SL", @ip.organization)
|
||||||
|
assert_equal(true, @ip.is_proxy?)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "work for a cloud hosting IP" do
|
||||||
|
@ip = IpGeolocation.create_or_update!("157.230.244.215")
|
||||||
|
assert_equal("DigitalOcean LLC", @ip.organization)
|
||||||
|
assert_equal(true, @ip.is_proxy?)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "work for a bogon IP" do
|
||||||
|
@ip = IpGeolocation.create_or_update!("103.10.192.0")
|
||||||
|
assert_equal(true, @ip.is_proxy?)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,11 +1,18 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class UserDeletionTest < ActiveSupport::TestCase
|
class UserDeletionTest < ActiveSupport::TestCase
|
||||||
|
setup do
|
||||||
|
@request = mock
|
||||||
|
@request.stubs(:remote_ip).returns("1.1.1.1")
|
||||||
|
@request.stubs(:user_agent).returns("Firefox")
|
||||||
|
@request.stubs(:session).returns(session_id: "1234")
|
||||||
|
end
|
||||||
|
|
||||||
context "an invalid user deletion" do
|
context "an invalid user deletion" do
|
||||||
context "for an invalid password" do
|
context "for an invalid password" do
|
||||||
should "fail" do
|
should "fail" do
|
||||||
@user = create(:user)
|
@user = create(:user)
|
||||||
@deletion = UserDeletion.new(@user, "wrongpassword")
|
@deletion = UserDeletion.new(@user, "wrongpassword", @request)
|
||||||
@deletion.delete!
|
@deletion.delete!
|
||||||
assert_includes(@deletion.errors[:base], "Password is incorrect")
|
assert_includes(@deletion.errors[:base], "Password is incorrect")
|
||||||
end
|
end
|
||||||
@@ -14,7 +21,7 @@ class UserDeletionTest < ActiveSupport::TestCase
|
|||||||
context "for an admin" do
|
context "for an admin" do
|
||||||
should "fail" do
|
should "fail" do
|
||||||
@user = create(:admin_user)
|
@user = create(:admin_user)
|
||||||
@deletion = UserDeletion.new(@user, "password")
|
@deletion = UserDeletion.new(@user, "password", @request)
|
||||||
@deletion.delete!
|
@deletion.delete!
|
||||||
assert_includes(@deletion.errors[:base], "Admins cannot delete their account")
|
assert_includes(@deletion.errors[:base], "Admins cannot delete their account")
|
||||||
end
|
end
|
||||||
@@ -24,7 +31,7 @@ class UserDeletionTest < ActiveSupport::TestCase
|
|||||||
context "a valid user deletion" do
|
context "a valid user deletion" do
|
||||||
setup do
|
setup do
|
||||||
@user = create(:user, name: "foo", email_address: build(:email_address))
|
@user = create(:user, name: "foo", email_address: build(:email_address))
|
||||||
@deletion = UserDeletion.new(@user, "password")
|
@deletion = UserDeletion.new(@user, "password", @request)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "blank out the email" do
|
should "blank out the email" do
|
||||||
|
|||||||
Reference in New Issue
Block a user