Skip to content

Instantly share code, notes, and snippets.

@philayres
Created October 31, 2013 13:30
Show Gist options
  • Save philayres/7249755 to your computer and use it in GitHub Desktop.
Save philayres/7249755 to your computer and use it in GitHub Desktop.
Simple One-Time-Token (nonce) generator / validator. Note: one time use of the token in the database is not included in this example, but validation of timeout and URI parameter correctness is
require 'digest'
class ApiAuth
# Pass params as hash, a secret used to salt the hash, action and controller, and any additional options
def self.generate_ottoken params, secret, action, controller, options={}
uri_set = []
sign_params = params.to_a
sign_params.sort! {|x,y| x <=> y }
sign_params.each do |k,v|
pname = k.to_s
# Use a subset of character substitutions for URI encoding
pval = v.to_s.gsub('%', '%25').gsub('&', '%26').gsub('=', '%3D').gsub('?', '%3F')
uri_set << "#{pname}=#{pval}"
end
uri_string = "#{secret}#{controller}/#{action}?#{uri_set.join('&')}"
options[:uri_string] = uri_string # Allow testing of the result
Digest::SHA256.hexdigest(uri_string)
end
def self.validate_ottoken params, secret, action, controller, options={}
ottoken = params[:ottoken]
timestamp = params[:timestamp]
raise "Incorrect parameters in validate_ottoken" unless ottoken && timestamp && !ottoken.empty?
timedout = (timestamp.to_i - current_timestamp.to_i).abs > max_time_difference(options)
raise "Request has timed out" if timedout
sign_params = params.dup
sign_params.delete(:ottoken)
otgen = generate_ottoken(sign_params, secret, action, controller, options)
return (otgen == ottoken)
end
def self.current_timestamp
Time.new.strftime('%s%3N')
end
def self.max_time_difference options={}
#Timeout in milliseconds
options[:max_timeout] || 30000
end
end
require './lib/api_server/api_auth'
describe ApiAuth do
before(:each) do
@api_auth = ApiAuth.new
end
it "should return same one time token" do
res = {}
# Check correct creation of an ottoken string with character encodings
t = Time.new.strftime('%s%3N')
params = {b: 123, c: 'abc', adg: 'hello phil', rand: 'this & this = a question?', timestamp: t, client: 'ajksf7862346fffs1'}
ot = ApiAuth.generate_ottoken(params, 'supersecret!', 'do', 'some_controller', res)
expected_response = 'supersecret!some_controller/do?adg=hello phil&b=123&c=abc&client=ajksf7862346fffs1&rand=this %26 this %3D a question%3F&timestamp='+t
res[:uri_string].should == expected_response
# Check result is repeatable
ot2 = ApiAuth.generate_ottoken(params, 'supersecret!', 'do', 'some_controller', res)
ot.should == ot2
# Check a timelag produces a different ottoken result
sleep(0.1)
t2 = Time.new.strftime('%s%3N')
params[:timestamp] = t2
ot_test = ApiAuth.generate_ottoken(params, 'supersecret!', 'do', 'some_controller', res)
puts res[:uri_string]
puts ot_test
res[:uri_string].should_not == expected_response
# Check timeout allows some lag before rejecting the timeout
sleep(1.0)
params[:ottoken] = ot_test
otres = ApiAuth.validate_ottoken(params, 'supersecret!', 'do', 'some_controller', res)
puts res[:uri_string]
otres.should == true
# Check ottoken timeout with very short max timeout (100ms)
res[:max_timeout]=100
expect {otres = ApiAuth.validate_ottoken(params, 'supersecret!', 'do', 'some_controller', res) }.to raise_error
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment