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 diff --git a/devise_crowdsec.gemspec b/devise_crowdsec.gemspec index c8018dd..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. @@ -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