Module: Raptcha

Defined in:
lib/raptcha.rb,
lib/raptcha.rb

Overview

the command line code

Defined Under Namespace

Modules: Encoder, Encryptor, Image Classes: BadInput, CLI, Error, Expired, NoInput

Constant Summary collapse

README =
<<-__
  NAME
    raptcha.rb

  SYNOPSIS
    super low drain bamage, K.I.S.S., storage-less, session-less,
    plugin-less, dependency-less, zero admin, single-source-file secure
    captcha system for ruby and/or rails.

    bitchin.

  DESCRIPTION
    raptcha manages image generation via a streaming controller.  the result
    is that *no* disk storage is ever needed for captcha images.  it also
    manages authentication via openssl(aes-256) encoded hidden fields which
    are relayed through the form submission process, obviating the need for
    session/database interaction for captcha validation.  the library is
    useful outside of rails, even from the command line.

    what this means to you is that you can have a nice looking, and easy to
    customize, safe captcha solution in about 1 minute that requires zero
    maintenance moving forward.

    see a sample image here
      http://github.com/ahoward/raptcha/blob/master/images/raptcha.png

  INSTALL
    1) INSTALL Image Magick 
        ~> which convert

    2) COPY A SINGLE FILE INTO YOUR RAILS APP
        ~> cp raptcha.rb ./app/lib/

    3) GENERATE THE CONROLLER
        ruby lib/raptcha.rb generate controller

    4) ADD A ROUTE
        match 'raptcha(/:action)', :controller => 'raptcha'

    5) PUT A RAPTCHA IMAGE AND FORM INPUT IN YOUR VIEW
        <%= Raptcha.input %>

    6) REQUIRE VALID RAPTCHA INPUT IN A CONTROLLER ACTION
        class SessionsController < ApplicationController
          def create 
            unless Raptcha.valid?(params)
              # ...
            end
          end
        end

    7) TRY THE EXAMPLES LOCALLY AT
        http://0.0.0.0:3000/raptcha/form
        http://0.0.0.0:3000/raptcha/inline

  URIS
    http://github.com/ahoward/raptcha
    http://codforpeople.com


  COMMAND LINE USAGE
    * make an image by hand
      ~> ruby lib/raptcha.rb image foreground:pink raptcha.png && open raptcha.png

    * generate the controller
      ~> ruby lib/rapcha.rb generate controller

  DOC
    less lib/raptcha.rb
__
Version =
'2.0.0'

Class Method Summary collapse

Class Method Details

.alphabetObject



369
370
371
# File 'lib/raptcha.rb', line 369

def alphabet
  @alphabet ||= ('A' .. 'Z').to_a
end

.close_enoughObject



227
228
229
230
231
232
233
234
235
# File 'lib/raptcha.rb', line 227

def close_enough
  @close_enough ||= {
    '0OoQ' => '0',
    '1l'   => '1',
    '2zZ'  => '2',
    '5sS'  => '5',
    'kKxX' => 'x',
  }
end

.fuzzy(word) ⇒ Object



283
284
285
286
287
288
289
# File 'lib/raptcha.rb', line 283

def fuzzy(word)
  result = word.to_s.downcase
  close_enough.each do |charset, replace|
    result.gsub!(%r"[#{ charset }]", replace)
  end
  result.upcase.strip
end

.fuzzy_match(a, b) ⇒ Object



291
292
293
# File 'lib/raptcha.rb', line 291

def fuzzy_match(a, b)
  fuzzy(a) == fuzzy(b)
end

.gravityObject



219
220
221
# File 'lib/raptcha.rb', line 219

def gravity
  @gravity ||= 'north'
end

.image(*args, &block) ⇒ Object



373
374
375
# File 'lib/raptcha.rb', line 373

def image(*args, &block)
  Raptcha::Image.create(*args, &block)
end

.img(options = {}) ⇒ Object



341
342
343
344
345
346
347
348
349
350
# File 'lib/raptcha.rb', line 341

def img(options = {})
  options = Raptcha.normalize(options)
  return(inline(options)) if options[:inline]
  route = options[:route] || Raptcha.route
  word = options[:word] || Raptcha.word
  encrypted_word = Encryptor.encrypt(word)
  %[
    <img src="#{ route }?e=#{ encrypted_word }" alt="raptcha.png" class="raptcha-image"/>
  ]
end

.inline(options = {}) ⇒ Object



352
353
354
355
356
357
# File 'lib/raptcha.rb', line 352

def inline(options = {})
  options = Raptcha.normalize(options)
  %[
    <img src="data:image/png;base64,#{ Image.inline(options)  }" alt="raptcha.png" class="raptcha-image"/>
  ]
end

.input(options = {}) ⇒ Object Also known as: tag



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/raptcha.rb', line 295

def input(options = {})
  options = Raptcha.normalize(options)

  options[:route] ||= Raptcha.route
  options[:word] ||= Raptcha.word
  options[:timebomb] ||= Raptcha.timebomb
  options[:gravity] ||= Raptcha.gravity

  encrypted_word = Encryptor.encrypt(options[:word])
  encrypted_timebomb = Encryptor.encrypt(options[:timebomb])

  west = north = east = south = nil

  case gravity.to_s
    when /w(est)?/
      west = Raptcha.img(options)
    when /n(orth)?/
      north = Raptcha.img(options) + '<br>'
    when /e(ast)?/
      east = Raptcha.img(options)
    when /s(outh)?/
      south = '<br>' + Raptcha.img(options)
  end

  html =
    <<-html
      <div class="raptcha">
        #{ north } #{ west }
        <input type="textarea" name="raptcha[t]" value="" class="raptcha-input"/>
        <input type="hidden" name="raptcha[w]" value="#{ encrypted_word }" class="raptcha-word"/>
        <input type="hidden" name="raptcha[b]" value="#{ encrypted_timebomb }" class="raptcha-timebomb"/>
        #{ east } #{ south }
      </div>
    html

  singleton_class =
    class << html
      self
    end
  word = options[:word]
  singleton_class.send(:define_method, :word){ word }

  html
end

.keyObject



211
212
213
# File 'lib/raptcha.rb', line 211

def key
  @key ||= Rails::Application.config.secret_token
end

.normalize(options) ⇒ Object



237
238
239
# File 'lib/raptcha.rb', line 237

def normalize(options)
  options.inject({}){|h, kv| h.update(kv.first.to_s.to_sym => kv.last) }
end

.render(controller, params) ⇒ Object



377
378
379
380
381
# File 'lib/raptcha.rb', line 377

def render(controller, params)
  controller.instance_eval do
    send_data(Raptcha.image(params), :type => 'image/png', :disposition => 'inline', :filename => 'raptcha.png')
  end
end

.routeObject



215
216
217
# File 'lib/raptcha.rb', line 215

def route
  @route ||= '/raptcha'
end

.timebombObject



359
360
361
# File 'lib/raptcha.rb', line 359

def timebomb
  Time.now.utc.to_i + Raptcha.ttl
end

.ttlObject



223
224
225
# File 'lib/raptcha.rb', line 223

def ttl
  @ttl ||= 30 * 60
end

.valid?(params) ⇒ Boolean

Returns:

  • (Boolean)


241
242
243
244
245
246
247
248
249
# File 'lib/raptcha.rb', line 241

def valid?(params)
  begin
    validate!(params)
  rescue NoInput
    nil
  rescue BadInput, Expired
    false
  end
end

.validate!(params) ⇒ Object



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/raptcha.rb', line 251

def validate!(params)
  params = Raptcha.normalize(params)

  if params.has_key?(:raptcha)
    raptcha = params[:raptcha]

    textarea = raptcha[:t]
    word = raptcha[:w]
    timebomb = raptcha[:b]

    raise NoInput unless(textarea and word and timebomb)

    word = Encryptor.decrypt(word)
    timebomb = Encryptor.decrypt(timebomb)

    begin
      timebomb = Integer(timebomb)
      timebomb = Time.at(timebomb).utc
      now = Time.now.utc
      raise Expired unless now < timebomb
    rescue
      raise Expired
    end

    raise BadInput unless fuzzy_match(word, textarea)

    textarea
  else
    validate!(:raptcha => params)
  end
end

.versionObject



77
# File 'lib/raptcha.rb', line 77

def Raptcha.version() Version end

.word(size = 6) ⇒ Object



363
364
365
366
367
# File 'lib/raptcha.rb', line 363

def word(size = 6)
  word = ''
  size.times{ word << alphabet[rand(alphabet.size - 1)]}
  word
end