JWT cookies

This commit is contained in:
Yessiest 2024-02-01 21:43:34 +04:00
parent dce0937e2e
commit 1e546aa417
6 changed files with 76 additions and 18 deletions

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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)

54
lib/landline/util/jwt.rb Normal file
View File

@ -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

View File

@ -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.