JWT cookies
This commit is contained in:
parent
dce0937e2e
commit
1e546aa417
|
@ -1,5 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'landline/util/jwt'
|
||||||
require_relative 'landline/server'
|
require_relative 'landline/server'
|
||||||
require_relative 'landline/path'
|
require_relative 'landline/path'
|
||||||
require_relative 'landline/probe'
|
require_relative 'landline/probe'
|
||||||
|
|
|
@ -69,6 +69,9 @@ module Landline
|
||||||
block
|
block
|
||||||
end
|
end
|
||||||
|
|
||||||
|
alias before preprocess
|
||||||
|
alias after postprocess
|
||||||
|
|
||||||
# Add a filter to the path.
|
# Add a filter to the path.
|
||||||
# Blocks path access if a filter returns false.
|
# Blocks path access if a filter returns false.
|
||||||
# @param block [#call]
|
# @param block [#call]
|
||||||
|
@ -81,7 +84,7 @@ module Landline
|
||||||
# Include an application as a child of path.
|
# Include an application as a child of path.
|
||||||
# @param filename [String]
|
# @param filename [String]
|
||||||
def plugin(filename)
|
def plugin(filename)
|
||||||
self.define_singleton_method(:run) do |object|
|
define_singleton_method(:run) do |object|
|
||||||
unless object.is_a? Landline::Node
|
unless object.is_a? Landline::Node
|
||||||
raise ArgumentError, "not a node instance or subclass instance"
|
raise ArgumentError, "not a node instance or subclass instance"
|
||||||
end
|
end
|
||||||
|
@ -89,9 +92,9 @@ module Landline
|
||||||
object
|
object
|
||||||
end
|
end
|
||||||
@origin.children.append(
|
@origin.children.append(
|
||||||
self.instance_eval(File.read(filename), filename)
|
instance_eval(File.read(filename), filename)
|
||||||
)
|
)
|
||||||
self.singleton_class.undef_method :run
|
singleton_class.undef_method :run
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -132,7 +132,7 @@ module Landline
|
||||||
# @note reads request.input - may nullify request.body.
|
# @note reads request.input - may nullify request.body.
|
||||||
# @return [Object]
|
# @return [Object]
|
||||||
def json
|
def json
|
||||||
JSON.load(request.input)
|
JSON.parse(request.input)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Open a file relative to current filepath
|
# Open a file relative to current filepath
|
||||||
|
|
|
@ -10,7 +10,7 @@ ParserCommon = Landline::Util::ParserCommon
|
||||||
|
|
||||||
if RUBY_ENGINE == 'jruby' # fix for JRuby
|
if RUBY_ENGINE == 'jruby' # fix for JRuby
|
||||||
OpenSSL::HMAC.define_singleton_method(:base64digest) do |*args|
|
OpenSSL::HMAC.define_singleton_method(:base64digest) do |*args|
|
||||||
Base64.encode64(OpenSSL::HMAC.digest(*args)).strip
|
Base64.strict_encode64(OpenSSL::HMAC.digest(*args)).strip
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -29,11 +29,11 @@ module Landline
|
||||||
# @option params [String, Date] "expires"
|
# @option params [String, Date] "expires"
|
||||||
# @raise Landline::ParsingError invalid cookie parameters
|
# @raise Landline::ParsingError invalid cookie parameters
|
||||||
def initialize(key, value, params = {})
|
def initialize(key, value, params = {})
|
||||||
unless key.match? HeaderRegexp::COOKIE_NAME
|
unless key.match?(/\A#{HeaderRegexp::COOKIE_NAME}\z/o)
|
||||||
raise Landline::ParsingError, "invalid cookie key: #{key}"
|
raise Landline::ParsingError, "invalid cookie key: #{key}"
|
||||||
end
|
end
|
||||||
|
|
||||||
unless value.match? HeaderRegexp::COOKIE_VALUE
|
unless value.match?(/\A#{HeaderRegexp::COOKIE_VALUE}\z/o)
|
||||||
raise Landline::ParsingError, "invalid cookie value: #{value}"
|
raise Landline::ParsingError, "invalid cookie value: #{value}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ module Landline
|
||||||
# @param sep [String] Hash separator
|
# @param sep [String] Hash separator
|
||||||
# @return [Boolean] whether value is signed and valid
|
# @return [Boolean] whether value is signed and valid
|
||||||
def verify(key, algorithm: "sha256", sep: "&")
|
def verify(key, algorithm: "sha256", sep: "&")
|
||||||
val, sig = @value.match(/\A(.*)#{sep}([A-Za-z0-9+\/=]+)\Z/).to_a[1..]
|
val, sig = @value.match(/\A(.*)#{sep}([A-Za-z0-9+\/=]+)\z/).to_a[1..]
|
||||||
return false unless val and sig
|
return false unless val and sig
|
||||||
|
|
||||||
sig == ::OpenSSL::HMAC.base64digest(algorithm, key, val)
|
sig == ::OpenSSL::HMAC.base64digest(algorithm, key, val)
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'openssl'
|
||||||
|
require 'json'
|
||||||
|
require 'base64'
|
||||||
|
|
||||||
|
if RUBY_ENGINE == 'jruby' # fix for JRuby
|
||||||
|
OpenSSL::HMAC.define_singleton_method(:base64digest) do |*args|
|
||||||
|
Base64.strict_encode64(OpenSSL::HMAC.digest(*args)).strip
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Landline
|
||||||
|
module Util
|
||||||
|
# JSON Web Token construction class
|
||||||
|
class JWT
|
||||||
|
# Create a new JWT token wrapper
|
||||||
|
# @param data [Hash, Array] JSON-formattable data
|
||||||
|
# @param halgo [String] Name of the hash algorithm to use
|
||||||
|
def initialize(data, halgo = "SHA256")
|
||||||
|
@halgo = halgo
|
||||||
|
@data = data
|
||||||
|
end
|
||||||
|
|
||||||
|
# Construct a string representation of the current token
|
||||||
|
# @param key [String]
|
||||||
|
# @return [String]
|
||||||
|
def make(key)
|
||||||
|
jsondata = @data.to_json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"hash" => @halgo
|
||||||
|
}.to_json,
|
||||||
|
jsondata,
|
||||||
|
OpenSSL::HMAC.digest(@halgo, key, jsondata)
|
||||||
|
].map(&Base64.method(:strict_encode64)).map(&:strip).join "&"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Construct an object from string
|
||||||
|
# @param input [String]
|
||||||
|
# @param key [String]
|
||||||
|
# @return [JWT, nil] returns nil if verification couldn't complete
|
||||||
|
def self.from_string(input, key)
|
||||||
|
halgoj, dataj, sig = input.split("&").map(&Base64.method(:strict_decode64))
|
||||||
|
halgo = JSON.parse(halgoj)["hash"]
|
||||||
|
return nil if OpenSSL::HMAC.digest(halgo, key, dataj) != sig
|
||||||
|
|
||||||
|
new(JSON.parse(dataj), halgo)
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_accessor :data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -8,28 +8,28 @@ module Landline
|
||||||
# (not exactly precise) Regular expressions for some RFC definitions
|
# (not exactly precise) Regular expressions for some RFC definitions
|
||||||
module HeaderRegexp
|
module HeaderRegexp
|
||||||
# Matches the RFC2616 definiton of token
|
# Matches the RFC2616 definiton of token
|
||||||
TOKEN = /[!-~&&[^()<>@,;:\\"\/\[\]?={}\t]]+/.freeze
|
TOKEN = /[!-~&&[^()<>@,;:\\"\/\[\]?={}\t]]+/
|
||||||
# Matches the RFC2616 definition of quoted-string
|
# Matches the RFC2616 definition of quoted-string
|
||||||
QUOTED = /"[\x0-\x7E&&[^\x1-\x8\xb-\x1f]]*(?<!\\)"/.freeze
|
QUOTED = /"[\x0-\x7E&&[^\x1-\x8\xb-\x1f]]*(?<!\\)"/
|
||||||
# Matches any CHAR except CTLs
|
# Matches any CHAR except CTLs
|
||||||
PRINTCHAR = /[\x2-\x7E]/.freeze
|
PRINTCHAR = /[\x2-\x7E]/
|
||||||
# Matches 1 or more CHARs excluding CTLs
|
# Matches 1 or more CHARs excluding CTLs
|
||||||
PRINTABLE = /#{PRINTCHAR}+/o.freeze
|
PRINTABLE = /#{PRINTCHAR}+/o
|
||||||
# Matches the RFC6265 definition of a cookie-octet
|
# Matches the RFC6265 definition of a cookie-octet
|
||||||
COOKIE_OCTET = /[\x21-\x7E&&[^",;\\]]*/.freeze
|
COOKIE_OCTET = /[\x21-\x7E&&[^",;\\]]*/
|
||||||
COOKIE_VALUE = /(?:#{QUOTED}|#{COOKIE_OCTET})/o.freeze
|
COOKIE_VALUE = /(?:#{QUOTED}|#{COOKIE_OCTET})/o
|
||||||
COOKIE_NAME = TOKEN
|
COOKIE_NAME = TOKEN
|
||||||
# Matches the RFC6265 definition of cookie-pair.
|
# Matches the RFC6265 definition of cookie-pair.
|
||||||
# Captures name (1) and value (2).
|
# Captures name (1) and value (2).
|
||||||
COOKIE_PAIR = /\A(#{COOKIE_NAME})=(#{COOKIE_VALUE})\Z/o.freeze
|
COOKIE_PAIR = /\A(#{COOKIE_NAME})=(#{COOKIE_VALUE})\z/o
|
||||||
# Matches a very abstract definition of a quoted header paramter.
|
# Matches a very abstract definition of a quoted header paramter.
|
||||||
# Captures name (1) and value (2).
|
# Captures name (1) and value (2).
|
||||||
PARAM_QUOTED = /\A(#{TOKEN})=?(#{QUOTED}|#{PRINTCHAR}*)\Z/o.freeze
|
PARAM_QUOTED = /\A(#{TOKEN})=?(#{QUOTED}|#{PRINTCHAR}*)\z/o
|
||||||
# Matches a very abstract definition of a header parameter.
|
# Matches a very abstract definition of a header parameter.
|
||||||
# Captures name (1) and value (2).
|
# Captures name (1) and value (2).
|
||||||
PARAM = /\A(#{TOKEN})=?(#{PRINTCHAR}*)\Z/o.freeze
|
PARAM = /\A(#{TOKEN})=?(#{PRINTCHAR}*)\z/o
|
||||||
# Specifically matches cookie parameters
|
# Specifically matches cookie parameters
|
||||||
COOKIE_PARAM = /\A(#{TOKEN})=?(#{QUOTED}|#{COOKIE_OCTET})\Z/o.freeze
|
COOKIE_PARAM = /\A(#{TOKEN})=?(#{QUOTED}|#{COOKIE_OCTET})\z/o
|
||||||
end
|
end
|
||||||
|
|
||||||
# Module for all things related to parsing HTTP and related syntax.
|
# Module for all things related to parsing HTTP and related syntax.
|
||||||
|
|
Loading…
Reference in New Issue