From 1a6a8f8f3e540e09a85263423fe8025e95d5fd23 Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Tue, 1 Oct 2024 23:03:04 +0300 Subject: [PATCH] And the files --- lib/jwt/claims_validator.rb | 18 ++++++ lib/jwt/verify.rb | 118 ++++++++++++++++++++++++++++++++++++ spec/jwt/jwa_spec.rb | 9 +++ 3 files changed, 145 insertions(+) create mode 100644 lib/jwt/claims_validator.rb create mode 100644 lib/jwt/verify.rb create mode 100644 spec/jwt/jwa_spec.rb diff --git a/lib/jwt/claims_validator.rb b/lib/jwt/claims_validator.rb new file mode 100644 index 00000000..4a00430b --- /dev/null +++ b/lib/jwt/claims_validator.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require_relative 'error' + +module JWT + class ClaimsValidator + def initialize(payload) + Deprecations.warning('The ::JWT::ClaimsValidator class is deprecated and will be removed in future version of ruby-jwt') + @payload = payload + end + + def validate! + Claims::Numeric.verify!(payload: @payload) + + true + end + end +end diff --git a/lib/jwt/verify.rb b/lib/jwt/verify.rb new file mode 100644 index 00000000..31720ab0 --- /dev/null +++ b/lib/jwt/verify.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require 'jwt/error' + +module JWT + # JWT verify methods + class Verify + DEFAULTS = { + leeway: 0 + }.freeze + + class << self + %w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub verify_required_claims].each do |method_name| + define_method method_name do |payload, options| + new(payload, options).send(method_name) + end + end + + def verify_claims(payload, options) + options.each do |key, val| + next unless key.to_s =~ /verify/ + + Verify.send(key, payload, options) if val + end + end + end + + def initialize(payload, options) + Deprecations.warning('The ::JWT::Verify class is deprecated and will be removed in future version of ruby-jwt') + @payload = payload + @options = DEFAULTS.merge(options) + end + + def verify_aud + return unless (options_aud = @options[:aud]) + + aud = @payload['aud'] + raise JWT::InvalidAudError, "Invalid audience. Expected #{options_aud}, received #{aud || ''}" if ([*aud] & [*options_aud]).empty? + end + + def verify_expiration + return unless contains_key?(@payload, 'exp') + raise JWT::ExpiredSignature, 'Signature has expired' if @payload['exp'].to_i <= (Time.now.to_i - exp_leeway) + end + + def verify_iat + return unless contains_key?(@payload, 'iat') + + iat = @payload['iat'] + raise JWT::InvalidIatError, 'Invalid iat' if !iat.is_a?(Numeric) || iat.to_f > Time.now.to_f + end + + def verify_iss + return unless (options_iss = @options[:iss]) + + iss = @payload['iss'] + + options_iss = Array(options_iss).map { |item| item.is_a?(Symbol) ? item.to_s : item } + + case iss + when *options_iss + nil + else + raise JWT::InvalidIssuerError, "Invalid issuer. Expected #{options_iss}, received #{iss || ''}" + end + end + + def verify_jti + options_verify_jti = @options[:verify_jti] + jti = @payload['jti'] + + if options_verify_jti.respond_to?(:call) + verified = options_verify_jti.arity == 2 ? options_verify_jti.call(jti, @payload) : options_verify_jti.call(jti) + raise JWT::InvalidJtiError, 'Invalid jti' unless verified + elsif jti.to_s.strip.empty? + raise JWT::InvalidJtiError, 'Missing jti' + end + end + + def verify_not_before + return unless contains_key?(@payload, 'nbf') + raise JWT::ImmatureSignature, 'Signature nbf has not been reached' if @payload['nbf'].to_i > (Time.now.to_i + nbf_leeway) + end + + def verify_sub + return unless (options_sub = @options[:sub]) + + sub = @payload['sub'] + raise JWT::InvalidSubError, "Invalid subject. Expected #{options_sub}, received #{sub || ''}" unless sub.to_s == options_sub.to_s + end + + def verify_required_claims + return unless (options_required_claims = @options[:required_claims]) + + options_required_claims.each do |required_claim| + raise JWT::MissingRequiredClaim, "Missing required claim #{required_claim}" unless contains_key?(@payload, required_claim) + end + end + + private + + def global_leeway + @options[:leeway] + end + + def exp_leeway + @options[:exp_leeway] || global_leeway + end + + def nbf_leeway + @options[:nbf_leeway] || global_leeway + end + + def contains_key?(payload, key) + payload.respond_to?(:key?) && payload.key?(key) + end + end +end diff --git a/spec/jwt/jwa_spec.rb b/spec/jwt/jwa_spec.rb new file mode 100644 index 00000000..fb2ded8d --- /dev/null +++ b/spec/jwt/jwa_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +RSpec.describe JWT::JWA do + describe '.create' do + it 'finds an algorithm' do + expect(described_class.create('HS256')).to be_a(JWT::JWA::Hmac) + end + end +end