Daily Code Reading #11 – Rack::Staging

I’m looking at the Rack::Staging middleware today. This middleware looks like a great idea for staging servers, since it requires all requests to have a valid staging code (cookie). If a user doesn’t have one yet, you can create an HTML page that lets them submit their staging code.

The Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# Hi! I'am rack middleware!
# I was born for protect you staging application from anonymous and prying
 
# My author's name was Aleksandr Koss. Mail him at kossnocorp@gmail.com
# Nice to MIT you!
 
module Rack
  class Staging
    def initialize app, options = {}
      @app = app
      @options = {
        :session_key => :staging_auth,
        :code_param => :code
      }.merge options
    end
 
    def call env
      request = Rack::Request.new env
 
      # Check code in session and return Rails call if is valid
 
      return @app.call env if \
        request.session[@options[:session_key]] != nil and
        request.session[@options[:session_key]] != '' and
        code_valid? request.session[@options[:session_key]]
 
      # If post method check :code_param value
 
      if request.post? and code_valid? request.params[@options[:code_param].to_s]
        request.session[@options[:session_key]] =
          request.params[@options[:code_param].to_s]
 
        [301, {'Location' => '/'}, ''] # Redirect if code is valid
      else
        render_staging
      end
    end
 
  private
 
    # Render staging html file
 
    def render_staging
      [200, {'Content-Type' => 'text/html'}, [
        ::File.open(@options[:template_path], 'rb').read
      ]]
    end
 
    # Validate code
 
    def code_valid? code
      @options[:auth_codes].include? code
    end
  end
end

Code

Review

There are three different cases I want to review in Rack::Staging.

1. No staging code

1
2
3
4
5
def render_staging
  [200, {'Content-Type' => 'text/html'}, [
    ::File.open(@options[:template_path], 'rb').read
  ]]
end

This is the first case a user will run into. It happens when they don’t have a staging code in their session yet. The request ends up falling through most of #call until render_staging is reached. render_staging then reads the HTML file and sends it back to the browser.

2. Posting a new staging code

1
2
3
4
5
6
if request.post? and code_valid? request.params[@options[:code_param].to_s]
  request.session[@options[:session_key]] = request.params[@options[:code_param].to_s]
  [301, {'Location' => '/'}, ''] # Redirect if code is valid
else
  # ...
end

When a new staging code is posted from the form, it runs the code above. If the staging code posted is valid, Rack::Staging will then set the staging code in the session and redirect back to the root.

3. Responding to a request that has a staging code

1
2
3
4
return @app.call env if \
  request.session[@options[:session_key]] != nil and
  request.session[@options[:session_key]] != '' and
  code_valid? request.session[@options[:session_key]]

The final case happens when a request already has a valid staging code. This would send the request to the next application in the Rack stack, letting the final app handle the response like normal.

I think Rack::Staging would be very useful. I’m using some HTTP basic authentication to block access to the different staging sites and it’s clunky at best. I’d like to see Rack::Staging have some tests added, I think there is an edge case that might not be handled correctly.