-
Notifications
You must be signed in to change notification settings - Fork 374
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
27 changed files
with
698 additions
and
517 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# frozen_string_literal: true | ||
|
||
module JWT | ||
module Claims | ||
DEFAULTS = { | ||
leeway: 0 | ||
}.freeze | ||
|
||
ClaimsContext = Struct.new(:payload, keyword_init: true) | ||
|
||
class << self | ||
def verify!(payload, options) | ||
options = DEFAULTS.merge(options) | ||
verify_aud(payload, options) | ||
verify_expiration(payload, options) | ||
verify_iat(payload, options) | ||
verify_iss(payload, options) | ||
verify_jti(payload, options) | ||
verify_not_before(payload, options) | ||
verify_sub(payload, options) | ||
verify_required_claims(payload, options) | ||
end | ||
|
||
def verify_aud(payload, options) | ||
return unless options[:verify_aud] | ||
|
||
Claims::Audience.new(expected_audience: options[:aud]).validate!(context: ClaimsContext.new(payload: payload)) | ||
end | ||
|
||
def verify_expiration(payload, options) | ||
return unless options[:verify_expiration] | ||
|
||
Claims::Expiration.new(leeway: options[:exp_leeway] || options[:leeway]).validate!(context: ClaimsContext.new(payload: payload)) | ||
end | ||
|
||
def verify_iat(payload, options) | ||
return unless options[:verify_iat] | ||
|
||
Claims::IssuedAt.new.validate!(context: ClaimsContext.new(payload: payload)) | ||
end | ||
|
||
def verify_iss(payload, options) | ||
return unless options[:verify_iss] | ||
|
||
Claims::Issuer.new(issuers: options[:iss]).validate!(context: ClaimsContext.new(payload: payload)) | ||
end | ||
|
||
def verify_jti(payload, options) | ||
return unless options[:verify_jti] | ||
|
||
Claims::JwtId.new(validator: options[:verify_jti]).validate!(context: ClaimsContext.new(payload: payload)) | ||
end | ||
|
||
def verify_not_before(payload, options) | ||
return unless options[:verify_not_before] | ||
|
||
Claims::NotBefore.new(leeway: options[:nbf_leeway] || options[:leeway]).validate!(context: ClaimsContext.new(payload: payload)) | ||
end | ||
|
||
def verify_sub(payload, options) | ||
return unless options[:verify_sub] | ||
return unless options[:sub] | ||
|
||
Claims::Subject.new(expected_subject: options[:sub]).validate!(context: ClaimsContext.new(payload: payload)) | ||
end | ||
|
||
def verify_required_claims(payload, options) | ||
return unless (options_required_claims = options[:required_claims]) | ||
|
||
Claims::Required.new(required_claims: options_required_claims).validate!(context: ClaimsContext.new(payload: payload)) | ||
end | ||
end | ||
end | ||
end | ||
|
||
require_relative 'claims/audience' | ||
require_relative 'claims/expiration' | ||
require_relative 'claims/issued_at' | ||
require_relative 'claims/issuer' | ||
require_relative 'claims/jwt_id' | ||
require_relative 'claims/not_before' | ||
require_relative 'claims/numeric' | ||
require_relative 'claims/required' | ||
require_relative 'claims/subject' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# frozen_string_literal: true | ||
|
||
module JWT | ||
module Claims | ||
class Audience | ||
def initialize(expected_audience:) | ||
@expected_audience = expected_audience | ||
end | ||
|
||
def validate!(context:, **_args) | ||
aud = context.payload['aud'] | ||
raise JWT::InvalidAudError, "Invalid audience. Expected #{expected_audience}, received #{aud || '<none>'}" if ([*aud] & [*expected_audience]).empty? | ||
end | ||
|
||
private | ||
|
||
attr_reader :expected_audience | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# frozen_string_literal: true | ||
|
||
module JWT | ||
module Claims | ||
class Expiration | ||
def initialize(leeway:) | ||
@leeway = leeway | ||
end | ||
|
||
def validate!(context:, **_args) | ||
return unless context.payload.is_a?(Hash) | ||
return unless context.payload.key?('exp') | ||
|
||
raise JWT::ExpiredSignature, 'Signature has expired' if context.payload['exp'].to_i <= (Time.now.to_i - leeway) | ||
end | ||
|
||
private | ||
|
||
attr_reader :leeway | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# frozen_string_literal: true | ||
|
||
module JWT | ||
module Claims | ||
class IssuedAt | ||
def validate!(context:, **_args) | ||
return unless context.payload.is_a?(Hash) | ||
return unless context.payload.key?('iat') | ||
|
||
iat = context.payload['iat'] | ||
raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(::Numeric) || iat.to_f > Time.now.to_f | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# frozen_string_literal: true | ||
|
||
module JWT | ||
module Claims | ||
class Issuer | ||
def initialize(issuers:) | ||
@issuers = Array(issuers).map { |item| item.is_a?(Symbol) ? item.to_s : item } | ||
end | ||
|
||
def validate!(context:, **_args) | ||
case (iss = context.payload['iss']) | ||
when *issuers | ||
nil | ||
else | ||
raise JWT::InvalidIssuerError, "Invalid issuer. Expected #{issuers}, received #{iss || '<none>'}" | ||
end | ||
end | ||
|
||
private | ||
|
||
attr_reader :issuers | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# frozen_string_literal: true | ||
|
||
module JWT | ||
module Claims | ||
class JwtId | ||
def initialize(validator:) | ||
@validator = validator | ||
end | ||
|
||
def validate!(context:, **_args) | ||
jti = context.payload['jti'] | ||
if validator.respond_to?(:call) | ||
verified = validator.arity == 2 ? validator.call(jti, context.payload) : validator.call(jti) | ||
raise(JWT::InvalidJtiError, 'Invalid jti') unless verified | ||
elsif jti.to_s.strip.empty? | ||
raise(JWT::InvalidJtiError, 'Missing jti') | ||
end | ||
end | ||
|
||
private | ||
|
||
attr_reader :validator | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# frozen_string_literal: true | ||
|
||
module JWT | ||
module Claims | ||
class NotBefore | ||
def initialize(leeway:) | ||
@leeway = leeway | ||
end | ||
|
||
def validate!(context:, **_args) | ||
return unless context.payload.is_a?(Hash) | ||
return unless context.payload.key?('nbf') | ||
|
||
raise JWT::ImmatureSignature, 'Signature nbf has not been reached' if context.payload['nbf'].to_i > (Time.now.to_i + leeway) | ||
end | ||
|
||
private | ||
|
||
attr_reader :leeway | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# frozen_string_literal: true | ||
|
||
module JWT | ||
module Claims | ||
class Numeric | ||
def self.validate!(payload:, **_args) | ||
return unless payload.is_a?(Hash) | ||
|
||
new(payload).validate! | ||
end | ||
|
||
NUMERIC_CLAIMS = %i[ | ||
exp | ||
iat | ||
nbf | ||
].freeze | ||
|
||
def initialize(payload) | ||
@payload = payload.transform_keys(&:to_sym) | ||
end | ||
|
||
def validate! | ||
validate_numeric_claims | ||
|
||
true | ||
end | ||
|
||
private | ||
|
||
def validate_numeric_claims | ||
NUMERIC_CLAIMS.each do |claim| | ||
validate_is_numeric(claim) if @payload.key?(claim) | ||
end | ||
end | ||
|
||
def validate_is_numeric(claim) | ||
return if @payload[claim].is_a?(::Numeric) | ||
|
||
raise InvalidPayload, "#{claim} claim must be a Numeric value but it is a #{@payload[claim].class}" | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# frozen_string_literal: true | ||
|
||
module JWT | ||
module Claims | ||
class Required | ||
def initialize(required_claims:) | ||
@required_claims = required_claims | ||
end | ||
|
||
def validate!(context:, **_args) | ||
required_claims.each do |required_claim| | ||
next if context.payload.is_a?(Hash) && context.payload.include?(required_claim) | ||
|
||
raise JWT::MissingRequiredClaim, "Missing required claim #{required_claim}" | ||
end | ||
end | ||
|
||
private | ||
|
||
attr_reader :required_claims | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# frozen_string_literal: true | ||
|
||
module JWT | ||
module Claims | ||
class Subject | ||
def initialize(expected_subject:) | ||
@expected_subject = expected_subject.to_s | ||
end | ||
|
||
def validate!(context:, **_args) | ||
sub = context.payload['sub'] | ||
raise(JWT::InvalidSubError, "Invalid subject. Expected #{expected_subject}, received #{sub || '<none>'}") unless sub.to_s == expected_subject | ||
end | ||
|
||
private | ||
|
||
attr_reader :expected_subject | ||
end | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.