├── .gitignore ├── LICENCE ├── README.md ├── consumer.rb ├── lib ├── oauth_test_wrapper.rb ├── oauth_test_wrapper │ ├── client.rb │ ├── message.rb │ └── response.rb ├── rack_oauth_provider.rb └── rack_oauth_provider │ ├── applications.erb │ ├── authorize.erb │ └── layout.erb ├── provider.rb ├── views ├── error.erb ├── index.erb ├── layout.erb ├── list.erb └── show.erb └── views_client ├── consumerkey.erb ├── error.erb ├── index.erb ├── layout.erb ├── list.erb └── show.erb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | provider.sqlite3 3 | consumer.sqlite3 4 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Michael Wood (michaelwood.com) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Sinatra Rack Middleware OAuth Provider 2 | === 3 | 4 | An experiment in creating a Sinatra OAuth Provider as Rack Middleware, a simple OAuth Consumer and API wrapper to tie it all together. 5 | 6 | The Rack Middleware takes a simple hash of OAuth protected paths (represented by regular expressions) and associated request methods. 7 | 8 | # To run the provider: 9 | ruby provider.rb 10 | Go to http://localhost:4567/ 11 | 12 | # To run the consumer: 13 | ruby consumer.rb -p 5678 14 | Go to http://localhost:5678/ 15 | 16 | # Requirements and Installation 17 | 18 | **Sinatra: http://sinatra.github.com/** 19 | 20 | sudo gem install sinatra 21 | 22 | **Datamapper: http://datamapper.org/** 23 | 24 | sudo gem install datamapper 25 | sudo gem install do_sqlite3 26 | 27 | **OAuth for Ruby: http://github.com/pelle/oauth** 28 | 29 | sudo gem install oauth 30 | 31 | **OAuth Provider for Ruby: http://github.com/halorgium/oauth_provider** 32 | 33 | Since there is no gem yet you will need to do the following: 34 | cd lib 35 | git clone git://github.com/halorgium/oauth_provider.git 36 | 37 | Thanks to pelle, halorgium (for the auth_provider), and singpolyma (for the simple Sinatra example)! And to the Vancouver "Ruby in the Rain" event for opening my eyes to Sinatra. You guys rock! 38 | 39 | Enjoy! -------------------------------------------------------------------------------- /consumer.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'oauth' 4 | require 'oauth/consumer' 5 | require 'dm-core' 6 | require 'dm-validations' 7 | require 'dm-serializer' 8 | require File.dirname(__FILE__) + '/lib/oauth_test_wrapper' 9 | 10 | DataMapper.setup(:default, "sqlite3:///#{Dir.pwd}/consumer.sqlite3") 11 | 12 | class Oauth 13 | include DataMapper::Resource 14 | 15 | property :id, Integer, :serial => true # primary serial key 16 | 17 | property :consumer_key, String, :nullable => false # cannot be null 18 | property :consumer_secret, String, :nullable => false # cannot be null 19 | 20 | property :request_token, String 21 | property :request_secret, String 22 | 23 | property :access_token, String 24 | property :access_secret, String 25 | end 26 | 27 | DataMapper.auto_upgrade! 28 | 29 | set :views, File.dirname(__FILE__) + '/views_client' 30 | 31 | before do 32 | @client ||= get_client 33 | 34 | if !@client.nil? 35 | if @client.access_token.nil? 36 | @client = get_access 37 | end 38 | end 39 | end 40 | 41 | error do 42 | exception = request.env['sinatra.error'] 43 | warn "%s: %s" % [exception.class, exception.message] 44 | warn exception.backtrace.join("\n") 45 | 46 | @error = "Oh my! Something went awry. (" + exception.message + ")" 47 | erb :error 48 | end 49 | 50 | # index! 51 | get '/' do 52 | erb :index 53 | end 54 | 55 | get '/messages' do 56 | if @client.nil? 57 | erb :consumerkey 58 | else 59 | if @client.access_token.nil? 60 | redirect '/' 61 | else 62 | @messages = @client.messages 63 | erb :list 64 | end 65 | end 66 | end 67 | 68 | post '/messages' do 69 | if @client.nil? 70 | erb :consumerkey 71 | else 72 | if @client.access_token.nil? 73 | redirect '/' 74 | else 75 | @message = @client.create_message(params[:message_name], params[:message_details]) 76 | redirect "/messages/#{@message.message_id}" 77 | end 78 | end 79 | end 80 | 81 | get '/messages/:message_id' do 82 | if @client.nil? 83 | erb :consumerkey 84 | else 85 | if @client.access_token.nil? 86 | redirect '/' 87 | else 88 | @message = @client.show_message(params[:message_id]) 89 | erb :show 90 | end 91 | end 92 | end 93 | 94 | post '/addconsumerkey' do 95 | oauth = Oauth.new 96 | oauth.consumer_key = params[:consumer_key] 97 | oauth.consumer_secret = params[:consumer_secret] 98 | oauth.save 99 | 100 | redirect '/messages' 101 | end 102 | 103 | private 104 | 105 | def get_client 106 | oauth = Oauth.first 107 | 108 | if !oauth.nil? 109 | clientDetails = {:consumer_key => oauth.consumer_key, :consumer_secret => oauth.consumer_secret} 110 | 111 | if oauth.request_token and oauth.request_secret 112 | clientDetails.merge!({:request_token => oauth.request_token, :request_token_secret => oauth.request_secret}) 113 | end 114 | 115 | if oauth.access_token and oauth.access_secret 116 | clientDetails.merge!({:access_token => oauth.access_token, :access_token_secret => oauth.access_secret}) 117 | end 118 | 119 | client = OAuthTestWrapper::Client.new(clientDetails) 120 | end 121 | end 122 | 123 | def get_access 124 | oauth = Oauth.first 125 | 126 | if !oauth.access_token or !oauth.access_secret 127 | 128 | if !oauth.request_token or !oauth.request_secret 129 | request_token = @client.get_request_token 130 | 131 | oauth.request_token = request_token.token 132 | oauth.request_secret = request_token.secret 133 | oauth.save 134 | 135 | redirect @client.authorize_url 136 | else 137 | access_token = @client.get_access_token 138 | 139 | oauth.access_token = access_token.token 140 | oauth.access_secret = access_token.secret 141 | oauth.save 142 | end 143 | 144 | end 145 | 146 | @client 147 | end -------------------------------------------------------------------------------- /lib/oauth_test_wrapper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'oauth' 3 | require 'oauth/consumer' 4 | require 'json' 5 | 6 | 7 | class OAuthTestWrapper 8 | API_SERVER = "http://localhost:4567" 9 | AUTH_SERVER = "http://localhost:4567" 10 | REQUEST_TOKEN_PATH = "/oauth/request_token" 11 | ACCESS_TOKEN_PATH = "/oauth/access_token" 12 | AUTHORIZATION_URL = "#{AUTH_SERVER}/oauth/authorize" 13 | MESSAGES_API_PATH = "/messages" 14 | 15 | class Error < RuntimeError 16 | end 17 | 18 | class ArgumentError < Error 19 | end 20 | 21 | class OAuthTestWrapperException < Error 22 | end 23 | end 24 | 25 | require File.dirname(__FILE__) + '/oauth_test_wrapper/client' 26 | require File.dirname(__FILE__) + '/oauth_test_wrapper/response' 27 | require File.dirname(__FILE__) + '/oauth_test_wrapper/message' 28 | -------------------------------------------------------------------------------- /lib/oauth_test_wrapper/client.rb: -------------------------------------------------------------------------------- 1 | class OAuthTestWrapper 2 | class Client 3 | attr_reader :access_token, :request_token, :consumer 4 | 5 | def initialize(options = {}) 6 | raise OAuthTestWrapper::ArgumentError, "OAuth Consumer Key and Secret required" if options[:consumer_key].nil? || options[:consumer_secret].nil? 7 | 8 | @consumer = OAuth::Consumer.new(options[:consumer_key], options[:consumer_secret], { 9 | :site => OAuthTestWrapper::API_SERVER, 10 | :request_token_path => OAuthTestWrapper::REQUEST_TOKEN_PATH, 11 | :access_token_path => OAuthTestWrapper::ACCESS_TOKEN_PATH, 12 | :authorize_url => OAuthTestWrapper::AUTHORIZATION_URL, 13 | :scheme=>:header, 14 | :http_method=>:get 15 | }) 16 | 17 | if options[:request_token] && options[:request_token_secret] 18 | @request_token = OAuth::RequestToken.new(@consumer, options[:request_token], options[:request_token_secret]) 19 | else 20 | @request_token = nil 21 | end 22 | 23 | if options[:access_token] && options[:access_token_secret] 24 | @access_token = OAuth::AccessToken.new(@consumer, options[:access_token], options[:access_token_secret]) 25 | else 26 | @access_token = nil 27 | end 28 | end 29 | 30 | # http://oauth.net/core/1.0#anchor9 31 | # OAuth Authentication is done in three steps: 32 | 33 | # 1. The Consumer obtains an unauthorized Request Token 34 | def get_request_token(force_token_regeneration = false) 35 | if force_token_regeneration || @request_token.nil? 36 | @request_token = @consumer.get_request_token 37 | end 38 | @request_token 39 | end 40 | 41 | # 2. The User authorizes the Request Token 42 | def authorize_url 43 | raise OAuthTestWrapper::ArgumentError, "call #get_request_token first" if @request_token.nil? 44 | @request_token.authorize_url 45 | end 46 | 47 | # 3. The Consumer exchanges the Request Token for an Access Token 48 | def get_access_token 49 | raise OAuthTestWrapper::ArgumentError, "call #get_request_token and have user authorize the token first" if @request_token.nil? 50 | @access_token = @request_token.get_access_token 51 | end 52 | 53 | # Now the wrapper goodness... 54 | 55 | # Get messages 56 | def messages(reload = false) 57 | raise OAuthTestWrapper::ArgumentError, "OAuth Access Token Required" unless @access_token 58 | 59 | if reload || @messages.nil? 60 | response = OAuthTestWrapper::Response.new(access_token.get('/messages.json', {'Accept'=>'application/json'})) 61 | 62 | @messages = response.data.map do |message| 63 | OAuthTestWrapper::Message.new(message) 64 | end 65 | end 66 | 67 | @messages 68 | end 69 | 70 | # Create a message 71 | def create_message(name, details) 72 | raise OAuthTestWrapper::ArgumentError, "OAuth Access Token Required" unless @access_token 73 | 74 | message = OAuthTestWrapper::Message.new({'name' => name, 'details' => details}) 75 | 76 | response = OAuthTestWrapper::Response.new(access_token.post('/messages.json', message.to_json, {'Accept'=>'application/json','Content-Type' => 'application/json'})) 77 | 78 | @message = OAuthTestWrapper::Message.new(response.data) 79 | end 80 | 81 | # Show a message 82 | def show_message(message_id, reload = false) 83 | raise OAuthTestWrapper::ArgumentError, "OAuth Access Token Required" unless @access_token 84 | 85 | if reload || @message.nil? 86 | response = OAuthTestWrapper::Response.new(access_token.get("/message/#{message_id}.json", {'Accept'=>'application/json'})) 87 | 88 | @message = OAuthTestWrapper::Message.new(response.data) 89 | end 90 | 91 | @message 92 | end 93 | 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /lib/oauth_test_wrapper/message.rb: -------------------------------------------------------------------------------- 1 | # A Message 2 | class OAuthTestWrapper 3 | class Message 4 | attr_accessor :message_id, :name, :details, :created_at, :updated_at 5 | 6 | def initialize(message) 7 | @message_id = message['id'] 8 | @name = message['name'] 9 | @details = message['details'] 10 | @created_at = message['created_at'] 11 | @updated_at = message['updated_at'] 12 | end 13 | 14 | def to_json 15 | { 16 | 'message' => { 17 | 'name' => @name, 18 | 'details' => @details, 19 | } 20 | }.to_json 21 | end 22 | 23 | def save(access_token) 24 | raise OAuthTestWrapper::OAuthTestWrapperException, "Missing message id" unless @message_id 25 | 26 | raise OAuthTestWrapper::ArgumentError, "OAuth Access Token Required" unless access_token 27 | 28 | response = OAuthTestWrapper::Response.new(access_token.put("/message/#{@message_id}.json", self.to_json, {'Accept'=>'application/json','Content-Type' => 'application/json'})) 29 | 30 | initialize(response.data) 31 | end 32 | 33 | def delete(access_token) 34 | raise OAuthTestWrapper::OAuthTestWrapperException, "Missing message id" unless @message_id 35 | 36 | raise OAuthTestWrapper::ArgumentError, "OAuth Access Token Required" unless access_token 37 | 38 | response = OAuthTestWrapper::Response.new(access_token.delete("/message/#{@message_id}.json", {'Accept'=>'application/json','Content-Type' => 'application/json'})) 39 | 40 | response.data 41 | end 42 | end 43 | 44 | end -------------------------------------------------------------------------------- /lib/oauth_test_wrapper/response.rb: -------------------------------------------------------------------------------- 1 | class OAuthTestWrapper 2 | class Response 3 | attr_reader :data 4 | 5 | # Parses the JSON response 6 | def initialize(response) 7 | case response.code 8 | when '500'; then raise OAuthTestWrapper::OAuthTestWrapperException, "Internal Server Error" 9 | when '400'; then raise OAuthTestWrapper::OAuthTestWrapperException, "Method Not Implemented Yet" 10 | end 11 | 12 | @data = JSON.parse(CGI::unescape(response.body)) 13 | end 14 | 15 | end 16 | end -------------------------------------------------------------------------------- /lib/rack_oauth_provider.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'oauth/request_proxy/rack_request' 3 | require File.dirname(__FILE__) + '/oauth_provider/lib/oauth_provider' 4 | 5 | class RackOAuthProvider < Sinatra::Base 6 | 7 | def initialize(app, paths) 8 | @paths = paths 9 | @app = app 10 | end 11 | 12 | set :root, File.dirname(__FILE__) 13 | set :views, File.dirname(__FILE__) + '/rack_oauth_provider' 14 | 15 | provider = OAuthProvider::create(:sqlite3, 'provider.sqlite3') 16 | #provider = OAuthProvider::create(:data_mapper, 'provider.sqlite3') 17 | 18 | mime :json, "application/json" 19 | 20 | # http://blog.joncrosby.me/post/72451217/a-world-of-middleware 21 | # this hackeration is required for sinatra to be a nice rack citizen 22 | error 404 do 23 | @app.call(env) 24 | end 25 | 26 | before do 27 | # check protected path agaist request path 28 | # see if we should proceed with oauth access confirmation... 29 | path = @request.path_info 30 | 31 | @paths.each do |protected_oauth_path,protected_oauth_method| 32 | if protected_oauth_path.match(path) 33 | 34 | if protected_oauth_method.include?(@request.request_method.to_s.downcase.to_sym) 35 | warn path + " was matched to " + @request.request_method.to_s + " " + protected_oauth_path.to_s 36 | 37 | oauth_confirm_access(provider) 38 | end 39 | end 40 | end 41 | end 42 | 43 | # OAuth routes 44 | get "/oauth/request_token" do 45 | provider.issue_request(request).query_string 46 | end 47 | 48 | get "/oauth/access_token" do 49 | if access_token = provider.upgrade_request(request) 50 | access_token.query_string 51 | else 52 | raise Sinatra::NotFound, "No such request token" 53 | end 54 | end 55 | 56 | # Authorize endpoints 57 | get "/oauth/authorize" do 58 | if @request_token = provider.backend.find_user_request(params[:oauth_token]) 59 | erb :authorize 60 | else 61 | raise Sinatra::NotFound, "No such request token" 62 | end 63 | end 64 | 65 | post "/oauth/authorize" do 66 | if request_token = provider.backend.find_user_request(params[:oauth_token]) 67 | if request_token.authorize 68 | redirect request_token.callback 69 | else 70 | raise "Could not authorize" 71 | end 72 | else 73 | raise Sinatra::NotFound, "No such request token" 74 | end 75 | end 76 | 77 | get "/oauth/applications" do 78 | @consumers = provider.consumers 79 | erb :applications 80 | end 81 | 82 | post '/oauth/applications' do 83 | begin 84 | @consumer = provider.add_consumer(params[:application_callback]) 85 | 86 | #redirect "/oauth/applications" 87 | @consumer_key = @consumer.token.shared_key 88 | @consumer_secret = @consumer.token.secret_key 89 | 90 | rescue Exception 91 | @error = "Failed to create a token!" 92 | end 93 | 94 | @consumers = provider.consumers 95 | 96 | erb :applications 97 | end 98 | 99 | private 100 | 101 | def oauth_confirm_access(provider) 102 | begin 103 | access = provider.confirm_access(@request) 104 | rescue Exception 105 | halt "No access! Please verify your OAuth access token and secret." 106 | end 107 | end 108 | 109 | 110 | end 111 | 112 | -------------------------------------------------------------------------------- /lib/rack_oauth_provider/applications.erb: -------------------------------------------------------------------------------- 1 | <% if @error %> 2 |
3 |

Oops!

4 | 5 |

<%= @error %>

6 |
7 | <% end %> 8 | 9 | <% if @consumer_key and @consumer_secret %> 10 |
11 |

Application created!

12 | 13 |

Save this information!

14 | 15 | Consumer Key: 16 | <%= @consumer_key %> 17 |
18 | 19 | Consumer Secret: 20 | <%= @consumer_secret %> 21 |
22 | <% end %> 23 | 24 | 25 |

My Applications

26 | 27 | <% if !@consumers.empty? %> 28 | <% @consumers.each do |consumer| %> 29 |
30 | <%= consumer.callback %> 31 |

32 | Consumer Key: <%= consumer.token.shared_key %> 33 |
34 | Consumer Secret: <%= consumer.token.secret_key %> 35 |

36 |
37 | <% end %> 38 | <% else %> 39 | You don't have any applications... yet. 40 | <% end %> 41 | 42 |

Create New Application

43 | 44 |
45 |
46 |
47 | Application Callback: 48 |
49 | 50 |
51 | 52 |
53 |
-------------------------------------------------------------------------------- /lib/rack_oauth_provider/authorize.erb: -------------------------------------------------------------------------------- 1 |

You are about to authorize (<%= @request_token.consumer.callback %>)

2 |
3 |

4 | 5 |

6 | 7 |

8 | 9 |

10 |
11 | -------------------------------------------------------------------------------- /lib/rack_oauth_provider/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sinatra OAuth API Test! 5 | 41 | 42 | 43 |
44 |

Sinatra OAuth API Rack Middleware

45 | 46 | 49 | 50 | <%= yield %> 51 |
52 | 53 | -------------------------------------------------------------------------------- /provider.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'dm-core' 4 | require 'dm-validations' 5 | require 'dm-timestamps' 6 | require 'dm-serializer' 7 | require File.dirname(__FILE__) + '/lib/rack_oauth_provider' 8 | 9 | # a list of oauth protected paths 10 | paths = { 11 | Regexp.new('\/messages.json') => [:get, :post], 12 | Regexp.new('\/messages\/[0-9]+.json') => [:get, :put, :delete], 13 | } 14 | 15 | use RackOAuthProvider, paths do 16 | end 17 | 18 | DataMapper.setup(:default, "sqlite3:///#{Dir.pwd}/provider.sqlite3") 19 | 20 | class Message 21 | include DataMapper::Resource 22 | 23 | property :id, Integer, :serial => true # primary serial key 24 | property :name, String, :nullable => false # cannot be null 25 | property :details, Text, :nullable => false # cannot be null 26 | 27 | property :created_at, DateTime 28 | property :updated_at, DateTime 29 | end 30 | 31 | DataMapper.auto_upgrade! 32 | 33 | set :views, File.dirname(__FILE__) + '/views' 34 | 35 | error do 36 | exception = request.env['sinatra.error'] 37 | warn "%s: %s" % [exception.class, exception.message] 38 | warn exception.backtrace.join("\n") 39 | 40 | @error = "Oh my! Something went awry. (" + exception.message + ")" 41 | erb :error 42 | end 43 | 44 | # index! 45 | get '/' do 46 | erb :index 47 | end 48 | 49 | # list 50 | get '/messages' do 51 | @messages = Message.all 52 | erb :list 53 | end 54 | 55 | # create 56 | post '/messages' do 57 | @message = Message.new(:name => params[:message_name], :details => params[:message_details]) 58 | if @message.save 59 | redirect "/messages/#{@message.id}" 60 | else 61 | redirect '/messages' 62 | end 63 | end 64 | 65 | # show 66 | get '/messages/:id' do 67 | @message = Message.get(params[:id]) 68 | if @message 69 | erb :show 70 | else 71 | redirect '/messages' 72 | end 73 | end 74 | 75 | get '/:model.json' do 76 | "#{params[:model]}".singularize.camelize.to_class.all.to_json 77 | end 78 | 79 | post '/:model.json' do 80 | name = params[:model].singularize 81 | record = name.camelize.to_class.new(JSON.parse(CGI::unescape(request.body.string))[name]) 82 | record.save 83 | record.to_json 84 | end 85 | 86 | get '/:model/:id.json' do 87 | "#{params[:model]}".singularize.camelize.to_class.get(params[:id]).to_json 88 | end 89 | 90 | put '/:model/:id.json' do 91 | name = params[:model].singularize 92 | record = name.camelize.to_class.get(params[:id]) 93 | record.update_attributes(JSON.parse(request.body.string)[name]) 94 | record.to_json 95 | end 96 | 97 | delete '/:model/:id.json' do 98 | record = "#{params[:model]}".singularize.camelize.to_class.get(params[:id]) 99 | result = record.to_json 100 | record.destroy 101 | result 102 | end 103 | 104 | private 105 | 106 | class String 107 | def to_class 108 | Kernel.const_get(self) 109 | end 110 | 111 | # http://rails.rubyonrails.org/classes/Inflector.html#M001629 112 | def camelize(first_letter_in_uppercase = true) 113 | if first_letter_in_uppercase 114 | self.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase } 115 | else 116 | self.first + camelize(self)[1..-1] 117 | end 118 | end 119 | 120 | end 121 | 122 | -------------------------------------------------------------------------------- /views/error.erb: -------------------------------------------------------------------------------- 1 |

Oops! There was an error.

2 | 3 |

<%= @error %>

-------------------------------------------------------------------------------- /views/index.erb: -------------------------------------------------------------------------------- 1 | Welcome to my Sinatra OAuth Test API App! -------------------------------------------------------------------------------- /views/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sinatra OAuth API Test! 5 | 41 | 42 | 43 |

44 |

Sinatra OAuth API Test

45 | 46 | 49 | 50 | <%= yield %> 51 |
52 | 53 | -------------------------------------------------------------------------------- /views/list.erb: -------------------------------------------------------------------------------- 1 |

My Messages

2 | 3 | <% if @messages.empty? %> 4 | You don't have any messages. 5 | <% else %> 6 | 11 | <% end %> 12 | 13 |

Create New Message

14 | 15 |
16 |
17 |
18 | Message Name: 19 |
20 |
21 |
22 | Details: 23 |
24 | 25 |
26 | 27 | 28 | 29 |
30 |
-------------------------------------------------------------------------------- /views/show.erb: -------------------------------------------------------------------------------- 1 |

<%= @message.name %>

2 | 3 |
4 |
<%= @message.details %>
5 | 6 |
7 | Created on <%= @message.created_at.strftime("%B %d, %Y at %I:%M %p") %> 8 |
9 |
-------------------------------------------------------------------------------- /views_client/consumerkey.erb: -------------------------------------------------------------------------------- 1 |

Add your Consumer Key and Secret

2 | 3 |
4 |
5 | Consumer Key: 6 |
7 | 8 |
9 |
10 | Consumer Secret: 11 |
12 | 13 |
14 | 15 | 16 |
-------------------------------------------------------------------------------- /views_client/error.erb: -------------------------------------------------------------------------------- 1 |

Oops! There was an error.

2 | 3 |

<%= @error %>

-------------------------------------------------------------------------------- /views_client/index.erb: -------------------------------------------------------------------------------- 1 | Welcome to my Sinatra OAuth Test API App! -------------------------------------------------------------------------------- /views_client/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sinatra OAuth API Test! 5 | 41 | 42 | 43 |

44 |

Sinatra OAuth API Test

45 | 46 | 49 | 50 | <%= yield %> 51 |
52 | 53 | -------------------------------------------------------------------------------- /views_client/list.erb: -------------------------------------------------------------------------------- 1 |

My Messages

2 | 3 | <% if @messages.empty? %> 4 | You don't have any messages. 5 | <% else %> 6 | 11 | <% end %> 12 | 13 |

Create New Message

14 | 15 |
16 |
17 |
18 | Message Name: 19 |
20 |
21 |
22 | Details: 23 |
24 | 25 |
26 | 27 | 28 | 29 |
30 |
-------------------------------------------------------------------------------- /views_client/show.erb: -------------------------------------------------------------------------------- 1 |

<%= @message.name %>

2 | 3 |
4 |
<%= @message.details %>
5 | 6 |
7 | Created on <%= @message.created_at %> 8 |
9 |
--------------------------------------------------------------------------------