From 535690a52f79146205e6a4588c29780c3fe82377 Mon Sep 17 00:00:00 2001 From: CBL Date: Tue, 11 Apr 2023 18:39:21 +0200 Subject: [PATCH 1/3] Draft notification on failure --- devise_crowdsec.gemspec | 7 ++--- lib/devise_crowdsec.rb | 18 +++++++++++- lib/devise_crowdsec/crowdsec_notifier.rb | 27 +++++++++++++++++ lib/devise_crowdsec/failure_app.rb | 37 ++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 lib/devise_crowdsec/crowdsec_notifier.rb create mode 100644 lib/devise_crowdsec/failure_app.rb diff --git a/devise_crowdsec.gemspec b/devise_crowdsec.gemspec index c8018dd..fba063a 100644 --- a/devise_crowdsec.gemspec +++ b/devise_crowdsec.gemspec @@ -30,9 +30,6 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - # Uncomment to register a new dependency of your gem - # spec.add_dependency "example-gem", "~> 1.0" - - # For more information and examples about making a new gem, check out our - # guide at: https://bundler.io/guides/creating_gem.html + spec.add_dependency "devise", ">= 4.0" + spec.add_dependency "faraday", ">= 1.0" end diff --git a/lib/devise_crowdsec.rb b/lib/devise_crowdsec.rb index 0963e4f..46246f6 100644 --- a/lib/devise_crowdsec.rb +++ b/lib/devise_crowdsec.rb @@ -1,8 +1,24 @@ # frozen_string_literal: true +require "devise" +require_relative "devise_crowdsec/crowdsec_notifier" +require_relative "devise_crowdsec/failure_app" require_relative "devise_crowdsec/version" +module Devise + # CrowdSec API URL configuration + mattr_accessor :crowdsec_api_url + @@crowdsec_api_url = nil + + # CrowdSec API key configuration + mattr_accessor :crowdsec_api_key + @@crowdsec_api_key = nil + + # CrowdSec threshold configuration + mattr_accessor :crowdsec_threshold + @@crowdsec_threshold = 5 +end + module DeviseCrowdsec class Error < StandardError; end - # Your code goes here... end diff --git a/lib/devise_crowdsec/crowdsec_notifier.rb b/lib/devise_crowdsec/crowdsec_notifier.rb new file mode 100644 index 0000000..476dad5 --- /dev/null +++ b/lib/devise_crowdsec/crowdsec_notifier.rb @@ -0,0 +1,27 @@ +require 'faraday' +require 'json' + +module DeviseCrowdsec + class CrowdsecNotifier + def self.send_alert(alert_data) + return unless Devise.crowdsec_api_key && Devise.crowdsec_api_url + + connection = Faraday.new(url: Devise.crowdsec_api_url) do |config| + config.headers['Content-Type'] = 'application/json' + config.headers['X-Api-Key'] = Devise.crowdsec_api_key + config.request :json + config.response :json + end + + response = connection.post do |req| + req.body = alert_data.to_json + end + + if response.success? + puts 'Alert sent successfully!' + else + puts "Error when sending the alert: #{response.body}" + end + end + end +end diff --git a/lib/devise_crowdsec/failure_app.rb b/lib/devise_crowdsec/failure_app.rb new file mode 100644 index 0000000..95a1458 --- /dev/null +++ b/lib/devise_crowdsec/failure_app.rb @@ -0,0 +1,37 @@ +module DeviseCrowdsec + class FailureApp < Devise::FailureApp + def respond + if warden_message == :invalid + store_failed_attempt + send_crowdsec_alert if exceeded_threshold? + end + super + end + + private + + def store_failed_attempt + attempts = session[:failed_attempts] || 0 + session[:failed_attempts] = attempts + 1 + end + + def exceeded_threshold? + threshold = Devise.crowdsec_threshold || 5 + session[:failed_attempts] >= threshold + end + + def send_crowdsec_alert + ip_address = request.remote_ip + alert_data = { + type: 'failed-login', + message: "Multiple failed login attempts from IP: #{ip_address}", + source: { + value: ip_address, + type: 'Ip', + }, + } + + CrowdsecNotifier.send_alert(alert_data) + end + end +end From 8d80c41b7575942748bd2d1bb48cc1c5191c9a46 Mon Sep 17 00:00:00 2001 From: CBL Date: Tue, 11 Apr 2023 19:19:22 +0200 Subject: [PATCH 2/3] Remove bin --- bin/console | 15 --------------- bin/setup | 8 -------- 2 files changed, 23 deletions(-) delete mode 100755 bin/console delete mode 100755 bin/setup diff --git a/bin/console b/bin/console deleted file mode 100755 index 9ccbdb1..0000000 --- a/bin/console +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require "bundler/setup" -require "devise_crowdsec" - -# You can add fixtures and/or initialization code here to make experimenting -# with your gem easier. You can also use a different console, if you like. - -# (If you use this, don't forget to add pry to your Gemfile!) -# require "pry" -# Pry.start - -require "irb" -IRB.start(__FILE__) diff --git a/bin/setup b/bin/setup deleted file mode 100755 index dce67d8..0000000 --- a/bin/setup +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -IFS=$'\n\t' -set -vx - -bundle install - -# Do any other automated setup that you need to do here From 5babb05ee6a01740355065f6333631f221efc274 Mon Sep 17 00:00:00 2001 From: CBL Date: Tue, 11 Apr 2023 19:19:31 +0200 Subject: [PATCH 3/3] Customize gemspec --- devise_crowdsec.gemspec | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/devise_crowdsec.gemspec b/devise_crowdsec.gemspec index fba063a..e2c3901 100644 --- a/devise_crowdsec.gemspec +++ b/devise_crowdsec.gemspec @@ -8,16 +8,16 @@ Gem::Specification.new do |spec| spec.authors = ["CBL"] spec.email = ["cyril@dilolabs.fr"] - spec.summary = "TODO: Write a short summary, because RubyGems requires one." - spec.description = "TODO: Write a longer description or delete this line." - spec.homepage = "TODO: Put your gem's website or public repo URL here." + spec.summary = "Devise CrowdSec extension" + spec.description = "A Devise extension that sends alerts to CrowdSec when a configurable number of authentication failures occur. This gem helps you integrate CrowdSec with your Ruby on Rails application using the Devise authentication library, providing additional security and monitoring of failed login attempts." + spec.homepage = "https://github.com/dilolabs/devise_crowdsec" spec.required_ruby_version = ">= 2.6.0" - spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'" + spec.metadata["allowed_push_host"] = "https://rubygems.org" spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here." - spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." + spec.metadata["source_code_uri"] = spec.homepage + spec.metadata["changelog_uri"] = "#{spec.homepage}/CHANGELOG.md" # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git.