diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..758e71b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +0.0.1 +----- + +- First release. diff --git a/README.md b/README.md new file mode 100644 index 0000000..19e69aa --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# RuCaptcha + + +This is a Captcha gem for Rails Application. It base on [mini_magick](https://github.com/minimagick/minimagick) to run ImageMagick command to draw Captcha image. + +So it's NO performance issue, and memory leak issue. + +Idea by: https://ruby-china.org/topics/20558#reply4 + +### Requirements + +- ImageMagick +- [mini_magick](https://github.com/minimagick/minimagick) + +### Usage + +Put rucaptcha in your `Gemfile`: + +``` +gem 'rucaptcha' +``` + +Create `config/initializes/rucaptcha.rb` + +```rb +RuCaptcha.configure do + # Number of chars, default: 4 + self.len = 4 + # Image width, default: 180 + self.width = 180 + # Image height, default: 48 + self.height = 48 + # Font size, default: 38 + self.font_size = 38 +end +``` + +Controller `app/controller/account_controller.rb` + +```rb +class AccountController < ApplicationController + def create + @user = User.new(params[:user]) + if verify_rucaptcha?(@user) && @user.save + redirect_to root_path, notice: 'Sign up successed.' + else + render 'account/new' + end + end +end +``` + +View `app/views/account/new.html.erb` + +```erb +
+ ... +
+ <%= rucaptcha_input_tag(class: 'form-control', placeholder: 'Input Captcha') %> + <%= rucaptcha_image_tag(alt: 'Captcha') %> +
+ ... +
+``` + diff --git a/app/controllers/ru_captcha/captcha_controller.rb b/app/controllers/ru_captcha/captcha_controller.rb new file mode 100644 index 0000000..a58b680 --- /dev/null +++ b/app/controllers/ru_captcha/captcha_controller.rb @@ -0,0 +1,11 @@ +module RuCaptcha + class CaptchaController < ActionController::Base + def index + headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' + headers['Pragma'] = 'no-cache' + + send_data generate_rucaptcha, disposition: 'inline', type: 'image/png' + end + end + +end diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..37c7de5 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,3 @@ +RuCaptcha::Engine.routes.draw do + root to: 'captcha#index' +end diff --git a/lib/rucaptcha.rb b/lib/rucaptcha.rb new file mode 100644 index 0000000..34f8167 --- /dev/null +++ b/lib/rucaptcha.rb @@ -0,0 +1,28 @@ +require 'active_support/core_ext' +require_relative 'rucaptcha/version' +require_relative 'rucaptcha/configuration' +require_relative 'rucaptcha/controller_helpers' +require_relative 'rucaptcha/view_helpers' +require_relative 'rucaptcha/engine' + +module RuCaptcha + class << self + def config + return @config if defined?(@config) + @config = Configuration.new + @config.len = 4 + @config.width = 180 + @config.height = 48 + @config.font_size = 38 + @config + end + + def configure(&block) + config.instance_exec(&block) + end + end +end + + +ActionController::Base.send :include, RuCaptcha::ControllerHelpers +ActionView::Base.send :include, RuCaptcha::ViewHelpers diff --git a/lib/rucaptcha/captcha.rb b/lib/rucaptcha/captcha.rb new file mode 100644 index 0000000..42ac59c --- /dev/null +++ b/lib/rucaptcha/captcha.rb @@ -0,0 +1,24 @@ +require 'posix-spawn' + +module RuCaptcha + class Captcha + class << self + def create(code) + color = "rgba(#{rand(100)},#{rand(100)},#{rand(100)}, 1)" + size = "#{RuCaptcha.config.width}x#{RuCaptcha.config.height}" + command = <<-CODE + convert -size #{size} -fill "#{color}" -background white \ + -draw 'stroke #{color} line #{rand(20)},#{rand(28)} #{rand(30) + 10},#{rand(48)}' \ + -draw 'stroke #{color} line #{rand(50)},#{rand(28)} #{rand(180)},#{rand(48)}' \ + -wave #{2 + rand(2)}x#{80 + rand(20)} \ + -gravity Center -sketch 2x19+#{rand(6)} -pointsize #{RuCaptcha.config.font_size} -implode 0.3 label:#{code.upcase} png:- + CODE + command.strip! + # puts command + pid, stdin, stdout, stderr = POSIX::Spawn.popen4(command) + Process.waitpid(pid) + stdout.read + end + end + end +end diff --git a/lib/rucaptcha/configuration.rb b/lib/rucaptcha/configuration.rb new file mode 100644 index 0000000..f01bf19 --- /dev/null +++ b/lib/rucaptcha/configuration.rb @@ -0,0 +1,5 @@ +module RuCaptcha + class Configuration + attr_accessor :width, :height, :font_size, :len + end +end diff --git a/lib/rucaptcha/controller_helpers.rb b/lib/rucaptcha/controller_helpers.rb new file mode 100644 index 0000000..b44444b --- /dev/null +++ b/lib/rucaptcha/controller_helpers.rb @@ -0,0 +1,22 @@ +module RuCaptcha + module ControllerHelpers + extend ActiveSupport::Concern + + included do + helper_method :verify_rucaptcha? + end + + def generate_rucaptcha + session[:_rucaptcha] = SecureRandom.hex(RuCaptcha.config.len / 2) + return Captcha.create(session[:_rucaptcha]) + end + + def verify_rucaptcha?(resource = nil) + right = params[:_rucaptcha].strip == session[:_rucaptcha] + if resource && resource.respond_to?(:errors) + resource.errors.add('rucaptcha', 'invalid') unless right + end + right + end + end +end diff --git a/lib/rucaptcha/engine.rb b/lib/rucaptcha/engine.rb new file mode 100644 index 0000000..a31f704 --- /dev/null +++ b/lib/rucaptcha/engine.rb @@ -0,0 +1,5 @@ +module RuCaptcha + class Engine < ::Rails::Engine + isolate_namespace RuCaptcha + end +end diff --git a/lib/rucaptcha/version.rb b/lib/rucaptcha/version.rb new file mode 100644 index 0000000..d7c0327 --- /dev/null +++ b/lib/rucaptcha/version.rb @@ -0,0 +1,4 @@ +module RuCaptcha + VERSION = '0.0.1' +end + diff --git a/lib/rucaptcha/view_helpers.rb b/lib/rucaptcha/view_helpers.rb new file mode 100644 index 0000000..dae0627 --- /dev/null +++ b/lib/rucaptcha/view_helpers.rb @@ -0,0 +1,13 @@ +module RuCaptcha + module ViewHelpers + def rucaptcha_input_tag(opts = {}) + opts[:name] = '_rucaptcha' + tag(:input, opts) + end + + def rucaptcha_image_tag(opts = {}) + opts[:class] = opts[:class] || 'rucaptcha-image' + image_tag(ru_captcha.root_path, opts) + end + end +end diff --git a/rucaptcha.gemspec b/rucaptcha.gemspec new file mode 100644 index 0000000..8f3bb3c --- /dev/null +++ b/rucaptcha.gemspec @@ -0,0 +1,21 @@ +lib = File.expand_path('../lib/', __FILE__) +$:.unshift lib unless $:.include?(lib) + +require "rucaptcha/version" + +Gem::Specification.new do |s| + s.name = 'rucaptcha' + s.version = RuCaptcha::VERSION + s.authors = ['Jason Lee'] + s.email = 'huacnlee@gmail.com' + s.files = Dir.glob("lib/**/*") + Dir.glob("app/**/*") + Dir.glob("config/**/*") + %w(README.md CHANGELOG.md) + s.homepage = 'https://github.com/huacnlee/rucaptcha' + s.require_paths = ['lib'] + s.summary = 'Captcha gem for Rails Application. It base on mini_magick to run ImageMagick command to draw Captcha image.' + + s.add_dependency 'mini_magick', '>= 4.0.0' + s.add_dependency 'posix-spawn', '>= 0.3.0' + + s.add_development_dependency 'rake' + s.add_development_dependency 'rspec', '>= 3.3.0' +end