Skip to content

Instantly share code, notes, and snippets.

@khy
Created January 31, 2012 02:45
Show Gist options
  • Save khy/1708389 to your computer and use it in GitHub Desktop.
Save khy/1708389 to your computer and use it in GitHub Desktop.
useless.io Authentication
module Useless
module Authentication
# The `Authentication::AccessToken` module defines the behavior for access-
# token-based authentication middleware. The middlewares are responsible
# only for providing the access token via the `#access_token_for_env`
# method.
module AccessToken
def initialize(app)
@app = app
end
def call(env)
# If we don't already have a user set in the environment,
unless env['useless.user']
# check to see if an access token was specified.
if access_token = access_token_for_env(env)
# If so, and a corresponding user can be found,
if user = Useless.mongo['users'].find_one('access_token' => access_token)
# set 'useless.user' in the environment.
env['useless.user'] = user
else
# Otherwise, return a 401 Unauthorized.
return [401, {'Content-Type' => 'text/plain'}, ["Invalid access token: #{access_token}"]]
end
end
end
@app.call(env)
end
private
def access_token_for_env(env)
end
end
end
end
source :rubygems
gem 'rack'
gem 'mongo'
gem 'bson'
gem 'bson_ext'
group :test do
gem 'rspec'
end
group :development do
gem 'heroku'
end
module Useless
# The `Useless::Mongo` module defines the interface for a Mongo helper
# class. Each helper class must override `#database`.
module Mongo
# Returns an instance of the appropriate helper class based upon the
# `RACK_ENV` environment variable (or the `env` parameter, if specified).
def self.for_env(env = nil)
case (env || ENV['RACK_ENV'])
when 'test' then Test.new
when 'production' then Production.new
else Development.new
end
end
# Simple access to a Mongo::Collection instance.
def [](collection)
db[collection]
end
# The environment's Mongo::DB instance.
def db
@db ||= self.connection.db(database)
end
# The environment's Mongo::Grid instance - a file store.
def grid
@grid ||= ::Mongo::Grid.new(db)
end
# The environment's Mongo::Connection instance.
def connection
@connection ||= ::Mongo::Connection.new(host)
end
# The host that `#connection` will use.
def host
'localhost'
end
# The database `#db` will use - must be overriden.
def database
end
class Development
include Mongo
def database
'useless_development'
end
end
class Test
include Mongo
def database
'useless_test'
end
end
class Production
include Mongo
# In production - on Heroku - we will need to use a URI to connect.
def connection
@connection ||= ::Mongo::Connection.from_uri(uri)
end
# The database can be extracted from the URI,
def database
uri.match(/.*\/(.*)$/)[1]
end
private
# and the URI is specified in the environment variable `MONGOLAB_URI`.
def uri
ENV['MONGOLAB_URI']
end
end
end
end
module Useless
module Authentication
# The `Authentication::QueryString` middleware attempt to retrieve the
# access token from the query string.
class QueryString
include AccessToken
private
def access_token_for_env(env)
Rack::Utils.parse_query(env['QUERY_STRING'])['access_token']
end
end
end
end
require File.dirname(__FILE__) + '/../../../spec_helper'
describe Useless::Authentication::QueryString do
def authenticated_app
app = lambda do |env|
body = env['useless.user'] ?
"user authenticated: #{env['useless.user']['handle']}" :
"unauthenticated"
[200, {'Content-Type' => 'text/plain'}, [body]]
end
Useless::Authentication::QueryString.new(app)
end
it 'should do nothing if `useless.user` is already set' do
Useless.mongo['users'].insert({'handle' => 'dph', 'access_token' => 'def456'})
res = Rack::MockRequest.new(authenticated_app).get('/resource?access_token=def456',
'useless.user' => {'handle' => 'khy', 'access_token' => 'abc123'})
res.should be_ok
res.body.should == 'user authenticated: khy'
end
it 'should do nothing if no access token is specified' do
res = Rack::MockRequest.new(authenticated_app).get('/resource')
res.should be_ok
res.body.should == 'unauthenticated'
end
it 'should return a 401 Unauthenticated if an access token is specified, but
does not correspond to a user' do
res = Rack::MockRequest.new(authenticated_app).get('/resource?access_token=abc123')
res.status.should == 401
res.body.should == 'Invalid access token: abc123'
end
it 'should set `useless.user` in the environment if a user can be found for
the specified access token' do
Useless.mongo['users'].insert({'handle' => 'khy', 'access_token' => 'abc123'})
res = Rack::MockRequest.new(authenticated_app).get('/resource?access_token=abc123')
res.should be_ok
res.body.should == 'user authenticated: khy'
end
end
module Useless
module Authentication
# The `Authentication::RequestHeader` middleware attempts to retrieve the
# access token from the `Authorization` request header.
class RequestHeader
include AccessToken
private
def access_token_for_env(env)
env['Authorization']
end
end
end
end
RSpec.configure do |config|
def clean_database
Useless.mongo.db.collections.each do |collection|
if !(collection.name =~ /^system\./)
collection.drop
end
end
end
config.before(:suite){ clean_database }
config.after(:each){ clean_database }
end
$:.unshift File.dirname(__FILE__)
require 'rubygems'
require 'bundler/setup'
require 'mongo'
module Useless
autoload :Mongo, 'useless/mongo'
def self.mongo
@mongo ||= Useless::Mongo.for_env
end
end
module Useless
module Authentication
autoload :AccessToken, 'useless/middleware/authentication/access_token'
autoload :QueryString, 'useless/middleware/authentication/query_string'
autoload :RequestHeader, 'useless/middleware/authentication/request_header'
end
def self.call(env)
map = SubdomainMap.new Base.new,
'street-art' => StreetArt.new
Rack::Builder.app do
use Authentication::QueryString
use Authentication::RequestHeader
run map
end.call(env)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment