diff --git a/lib/roda/proxy.rb b/lib/roda/proxy.rb index ff97222..0aeeccf 100644 --- a/lib/roda/proxy.rb +++ b/lib/roda/proxy.rb @@ -34,12 +34,48 @@ class Roda # Proxies the request, forwarding all headers except +Host+ which is # rewritten to be the destination host. The response headers, body and # status are returned to the client. + def verify_and_decrypt_session_cookie(cookie, secret_key_base) + cookie = CGI.unescape(cookie) + + ################# + # generate keys # + ################# + encrypted_cookie_salt = 'encrypted cookie' # default: Rails.application.config.action_dispatch.encrypted_cookie_salt + encrypted_signed_cookie_salt = 'signed encrypted cookie' # default: Rails.application.config.action_dispatch.encrypted_signed_cookie_salt + iterations = 1000 + key_size = 64 + secret = OpenSSL::PKCS5.pbkdf2_hmac_sha1(secret_key_base, encrypted_cookie_salt, iterations, key_size)[0, OpenSSL::Cipher.new('aes-256-cbc').key_len] + sign_secret = OpenSSL::PKCS5.pbkdf2_hmac_sha1(secret_key_base, encrypted_signed_cookie_salt, iterations, key_size) + + ########## + # Verify # + ########## + data, digest = cookie.split('--') + raise 'invalid message' unless digest == OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, sign_secret, data) + # you better use secure compare instead of `==` to prevent time based attact, + # ref: ActiveSupport::SecurityUtils.secure_compare + + ########### + # Decrypt # + ########### + encrypted_message = Base64.strict_decode64(data) + encrypted_data, iv = encrypted_message.split('--').map{|v| Base64.strict_decode64(v) } + cipher = OpenSSL::Cipher.new('aes-256-cbc') + cipher.decrypt + cipher.key = secret + cipher.iv = iv + decrypted_data = cipher.update(encrypted_data) + decrypted_data << cipher.final + + Marshal.load(decrypted_data) + end def proxy client = NetX::HTTPUnix.new(_sock_url) request_class = Net::HTTP.const_get(:"#{env['REQUEST_METHOD'].to_s.downcase.capitalize}") req = request_class.new(_proxy_url.to_s, _proxy_headers) env_body = env["rack.input"].read env_content_type = env['CONTENT_TYPE'] + secret_key_base = YAML.load(File.read(File.expand_path("../../../../config/secrets.yml",__FILE__)))['production']['secret_key_base'] unless env_body.nil? req.body = env_body end @@ -48,6 +84,21 @@ class Roda end f_response = client.request(req) f_response.content_length = nil #prevent duplicate header Content-Length and content-length + # if env['REQUEST_METHOD'].to_s.downcase == 'post' + # cookie = f_response['set-cookie'].to_s.split(/[;,]\s?/).map do |s| + # k,v = s.split('=', 2) + # [k, v.to_s] + # end.to_h + # puts ["cookie", cookie] + # if cookie['_orbit_store_session'] + # secret_key_base = YAML.load(File.read(File.expand_path("../../../../config/secrets.yml",__FILE__)))['production']['secret_key_base'] + # puts verify_and_decrypt_session_cookie(cookie['_orbit_store_session'], secret_key_base) rescue {} + # puts "---------------------------------" + # print f_response.get_fields('set-cookie') #['set-cookie'] + # puts nil + # puts "=================================" + # end + # end _respond(f_response) end @@ -104,9 +155,20 @@ class Roda def _respond(proxied_response) response.status = proxied_response.code.to_i proxied_response - .each_header.to_h + .to_hash .reject { |k, v| k.downcase == 'transfer-encoding' } - .each { |k, v| response[k] = v } + .each { |k, v| response[k] = v.join("\n") } + # cookie = proxied_response['set-cookie'].to_s.split('; ').map{|s| k,v = s.split('='); [k, v.to_s]}.to_h + # if cookie['_orbit_store_session'] + # secret_key_base = YAML.load(File.read(File.expand_path("../../../../config/secrets.yml",__FILE__)))['production']['secret_key_base'] + # puts verify_and_decrypt_session_cookie(cookie['_orbit_store_session'], secret_key_base) rescue {} + # end + # puts proxied_response + # .each_header.to_h + # proxied_response['set-cookie'].split('; ').map do |s| + # k,v = s.split('=') + # response.set_cookie(k,v) + # end response.write(proxied_response.body) end diff --git a/lib/session_store.rb b/lib/session_store.rb new file mode 100644 index 0000000..9304999 --- /dev/null +++ b/lib/session_store.rb @@ -0,0 +1,49 @@ +# Be sure to restart your server when you modify this file. +require 'rack/utils' +module Rack + module Utils + def self.set_cookie_header!(header, key, value) + case value + when Hash + domain = "; domain=" + value[:domain] if value[:domain] + path = "; path=" + value[:path] if value[:path] + max_age = "; max-age=" + value[:max_age] if value[:max_age] + expires = "; expires=" + + rfc2822(value[:expires].clone.gmtime) if value[:expires] + secure = "; secure" if value[:secure] + httponly = "; HttpOnly" if value[:httponly] + same_site = + case value[:same_site] + when false, nil + nil + when :none, 'None', :None + '; SameSite=None' + when :lax, 'Lax', :Lax + '; SameSite=Lax' + when true, :strict, 'Strict', :Strict + '; SameSite=Strict' + else + raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}" + end + value = value[:value] + end + value = [value] unless Array === value + cookie = escape(key) + "=" + + value.map { |v| escape v }.join("&") + + "#{domain}#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}" + + case header["Set-Cookie"] + when nil, '' + header["Set-Cookie"] = cookie + when String + header["Set-Cookie"] = [header["Set-Cookie"], cookie].join("\n") + when Array + header["Set-Cookie"] = (header["Set-Cookie"] + [cookie]).join("\n") + end + + nil + end + end +end +#Orbit::Application.config.session_store :cookie_store, key: '_orbit_store_session', expire_after: 1.week, same_site: :lax, path: "/", httponly: true +Orbit::Application.config.session_store :cookie_store, key: '_orbit_store_session', same_site: :lax, path: "/", httponly: true