Module: Raptcha
- Defined in:
- lib/raptcha.rb,
lib/raptcha.rb
Overview
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
.alphabet ⇒ Object
369
370
371
|
# File 'lib/raptcha.rb', line 369
def alphabet
@alphabet ||= ('A' .. 'Z').to_a
end
|
.close_enough ⇒ Object
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
|
.gravity ⇒ Object
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
|
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
|
.key ⇒ Object
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
|
.route ⇒ Object
215
216
217
|
# File 'lib/raptcha.rb', line 215
def route
@route ||= '/raptcha'
end
|
.timebomb ⇒ Object
359
360
361
|
# File 'lib/raptcha.rb', line 359
def timebomb
Time.now.utc.to_i + Raptcha.ttl
end
|
.ttl ⇒ Object
223
224
225
|
# File 'lib/raptcha.rb', line 223
def ttl
@ttl ||= 30 * 60
end
|
.valid?(params) ⇒ 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
|
.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
|