fresh_jwt 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3db90e474ada5fe77cce913c8cfeab01a059f22859b83d743dcf1ca9254d7fbc
4
+ data.tar.gz: fb524e5cbe83e56733e8294741c9f104a9535fa710a8dfed4af761db0334420d
5
+ SHA512:
6
+ metadata.gz: e3aa6737e66e4ce4f6c721a8a72c1ef2aa428026cba551edb792e1ba43463b3b2bed5b743678293e06c3eddcc198ef23c364ed17ff5e5b66f7e37a2f33a2c3fd
7
+ data.tar.gz: 387704eb2eefb1c2cce0b88d1c82fd152d5e6faef365b43cf8788d67d44294c9f98ed49c12e7f7b75d4b447f53137d68831dd02ee2f854502e2b160d7d4f15bf
data/.gitignore ADDED
@@ -0,0 +1,57 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ # Ignore Byebug command history file.
17
+ .byebug_history
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ #
33
+ # vendor/Pods/
34
+
35
+ ## Documentation cache and generated files:
36
+ /.yardoc/
37
+ /_yardoc/
38
+ /doc/
39
+ /rdoc/
40
+
41
+ ## Environment normalization:
42
+ /.bundle/
43
+ /vendor/bundle
44
+ /lib/bundler/man/
45
+
46
+ # for a library or gem, you might want to ignore these files since the code is
47
+ # intended to run in multiple environments; otherwise, check them in:
48
+ # Gemfile.lock
49
+ # .ruby-version
50
+ # .ruby-gemset
51
+
52
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
53
+ .rvmrc
54
+
55
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
+ # .rubocop-https?--*
57
+ tmp.rb
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ # A sample Gemfile
3
+ source "https://rubygems.org"
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ ruby "2.7.3"
8
+
9
+ gem 'jwt'
10
+ gem 'dry-initializer'
11
+ gem 'dry-validation'
12
+ gem 'dry-monads'
13
+ gem 'rspec'
14
+ # https://github.com/garyf/jwt_claims
15
+ # gem 'jwt_claims'
16
+
17
+ gem 'pry'
18
+ gem 'timecop'
data/Gemfile.lock ADDED
@@ -0,0 +1,81 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ coderay (1.1.3)
5
+ concurrent-ruby (1.1.9)
6
+ diff-lcs (1.4.4)
7
+ dry-configurable (0.12.1)
8
+ concurrent-ruby (~> 1.0)
9
+ dry-core (~> 0.5, >= 0.5.0)
10
+ dry-container (0.8.0)
11
+ concurrent-ruby (~> 1.0)
12
+ dry-configurable (~> 0.1, >= 0.1.3)
13
+ dry-core (0.6.0)
14
+ concurrent-ruby (~> 1.0)
15
+ dry-equalizer (0.3.0)
16
+ dry-inflector (0.2.0)
17
+ dry-initializer (3.0.4)
18
+ dry-logic (1.2.0)
19
+ concurrent-ruby (~> 1.0)
20
+ dry-core (~> 0.5, >= 0.5)
21
+ dry-monads (1.3.5)
22
+ concurrent-ruby (~> 1.0)
23
+ dry-core (~> 0.4, >= 0.4.4)
24
+ dry-equalizer
25
+ dry-schema (1.6.2)
26
+ concurrent-ruby (~> 1.0)
27
+ dry-configurable (~> 0.8, >= 0.8.3)
28
+ dry-core (~> 0.5, >= 0.5)
29
+ dry-initializer (~> 3.0)
30
+ dry-logic (~> 1.0)
31
+ dry-types (~> 1.5)
32
+ dry-types (1.5.1)
33
+ concurrent-ruby (~> 1.0)
34
+ dry-container (~> 0.3)
35
+ dry-core (~> 0.5, >= 0.5)
36
+ dry-inflector (~> 0.1, >= 0.1.2)
37
+ dry-logic (~> 1.0, >= 1.0.2)
38
+ dry-validation (1.6.0)
39
+ concurrent-ruby (~> 1.0)
40
+ dry-container (~> 0.7, >= 0.7.1)
41
+ dry-core (~> 0.4)
42
+ dry-equalizer (~> 0.2)
43
+ dry-initializer (~> 3.0)
44
+ dry-schema (~> 1.5, >= 1.5.2)
45
+ jwt (2.2.3)
46
+ method_source (1.0.0)
47
+ pry (0.14.1)
48
+ coderay (~> 1.1)
49
+ method_source (~> 1.0)
50
+ rspec (3.10.0)
51
+ rspec-core (~> 3.10.0)
52
+ rspec-expectations (~> 3.10.0)
53
+ rspec-mocks (~> 3.10.0)
54
+ rspec-core (3.10.1)
55
+ rspec-support (~> 3.10.0)
56
+ rspec-expectations (3.10.1)
57
+ diff-lcs (>= 1.2.0, < 2.0)
58
+ rspec-support (~> 3.10.0)
59
+ rspec-mocks (3.10.2)
60
+ diff-lcs (>= 1.2.0, < 2.0)
61
+ rspec-support (~> 3.10.0)
62
+ rspec-support (3.10.2)
63
+ timecop (0.9.4)
64
+
65
+ PLATFORMS
66
+ x86_64-darwin-20
67
+
68
+ DEPENDENCIES
69
+ dry-initializer
70
+ dry-monads
71
+ dry-validation
72
+ jwt
73
+ pry
74
+ rspec
75
+ timecop
76
+
77
+ RUBY VERSION
78
+ ruby 2.7.3p183
79
+
80
+ BUNDLED WITH
81
+ 2.2.17
data/README.md ADDED
@@ -0,0 +1 @@
1
+ # fresh_jwt
data/bin/console ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/fresh_jwt'
4
+
5
+ require 'pry'
6
+ Pry.start
@@ -0,0 +1,18 @@
1
+ module FreshJwt
2
+ class ContractError < StandardError
3
+ DEFAULT_MESSAGE = 'something wrong with Contract'
4
+ def initialize(msg = DEFAULT_MESSAGE, exception_type='custom')
5
+ @exception_type = exception_type
6
+ super(msg)
7
+ end
8
+ def message
9
+ super
10
+ end
11
+ end
12
+ end
13
+
14
+ class IssuerContract < Dry::Validation::Contract
15
+ params do
16
+ required(:algorithm).filled(:string)
17
+ end
18
+ end
data/lib/fresh_jwt.rb ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/validation'
4
+ require 'jwt'
5
+ require 'securerandom'
6
+
7
+ require_relative 'fresh_jwt/contract_validator'
8
+ require_relative 'fresh_jwt/expiration'
9
+ require_relative 'contracts/issuer_contract'
10
+ require_relative 'fresh_jwt/payload'
11
+ require_relative 'fresh_jwt/store'
12
+ require_relative 'fresh_jwt/version'
13
+ require_relative 'fresh_jwt/validator'
14
+ require_relative 'fresh_jwt/entity/access_token'
15
+ require_relative 'fresh_jwt/issuer'
16
+
17
+
18
+ # require 'dry/monads'
19
+ # TODO payload as dry entity
20
+
21
+
22
+ module FreshJwt
23
+
24
+
25
+ class Decoder
26
+ extend Dry::Initializer
27
+
28
+ option :algorithm, default: -> { 'HS256' }
29
+ option :secret, default: -> { 'SECRET' }
30
+
31
+ def decode(token:)
32
+ JWT.decode(token, secret, algorithm)
33
+ end
34
+ end
35
+
36
+
37
+ end
@@ -0,0 +1,24 @@
1
+ require 'dry/monads'
2
+ #это штука типа оборачивает монады...
3
+ require 'dry/monads/do'
4
+ module FreshJwt
5
+ class ContractValidator
6
+ include Dry::Monads[:result]
7
+ include Dry::Monads::Do.for(:call)
8
+
9
+ def call(contract, params)
10
+ result = contract.new.call(params)
11
+ if result.success?
12
+ Success(result)
13
+ else
14
+ Failure(result.errors.to_h)
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+
21
+
22
+ #def validate
23
+ # return Success(data) || Failure(:not_valid)
24
+ #end
@@ -0,0 +1,25 @@
1
+ module FreshJwt
2
+ module Entity
3
+
4
+ class Token
5
+ extend Dry::Initializer
6
+ option :name, proc(&:to_s), default: -> { self.class.to_s.split('::').last }
7
+ option :token, proc(&:to_s)
8
+ option :issued_at, proc(&:to_i), default: -> { Time.now.to_i }
9
+
10
+ end
11
+
12
+ class AccessToken < Token
13
+ option :payload, Payload, optional: true
14
+ option :expired_at, proc(&:to_i), default: -> { issued_at + FreshJwt::Expiration::ACCESS }
15
+ end
16
+
17
+ class RefreshToken < Token
18
+ option :expired_at, proc(&:to_i), default: -> { issued_at + FreshJwt::Expiration::REFRESH }
19
+ #https://auth0.com/docs/tokens/refresh-tokens/disable-refresh-token-rotation
20
+ option :behavior_type, proc(&:to_s), default: -> {'non-rotation'} #or rotation
21
+ end
22
+
23
+
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ module FreshJwt
2
+ module Expiration
3
+ def self.minutes v
4
+ v * 60
5
+ end
6
+ ACCESS = Expiration.minutes(5)
7
+ REFRESH = Expiration.minutes(60)
8
+ end
9
+ end
@@ -0,0 +1,53 @@
1
+ module FreshJwt
2
+
3
+ class Issuer
4
+ extend Dry::Initializer
5
+ include Dry::Monads[:result, :do]
6
+ REFRESH_EXPIRATION = 60*60*24
7
+
8
+ #Payload.new(extend: val )
9
+ option :payload, ->(hash){ Payload.new(exp: FreshJwt::Expiration::ACCESS, extend: hash) }, default: -> { Payload.new } #why we need new i do not understand, coz class is callable
10
+ # TODO: describe enum type for 2 algo
11
+ option :algorithm, default: -> { 'HS256' } #RS256
12
+ option :secret, default: -> { 'SECRET' }
13
+ option :refresh_token, proc(&:to_s), default: -> { SecureRandom.hex }
14
+ option :tokens_repo, default: -> { Store }
15
+
16
+ def call
17
+ validate_params params
18
+ token = JWT.encode(payload.to_hash, secret, algorithm)
19
+ access_token = Entity::AccessToken.new(token: token)
20
+ refresh_token = Entity::RefreshToken.new(token: refresh_token)
21
+
22
+ yield tokens_repo.single_transaction access_token
23
+ yield tokens_repo.single_transaction refresh_token
24
+
25
+ #result = tokens_repo.transaction do
26
+ # tokens_repo.save access_token
27
+ # tokens_repo.save refresh_token
28
+ #end
29
+ #return result unless result.success?
30
+
31
+ #tokens_repo.save Entity::AccessToken.new(
32
+ # token: token
33
+ #)
34
+ #tokens_repo.save Entity::RefreshToken.new(
35
+ # token: refresh_token
36
+ #)
37
+ return access_token, refresh_token
38
+ end
39
+
40
+ def params
41
+ @params ||= self.class.dry_initializer.attributes(self)
42
+ end
43
+
44
+ def validate_params params
45
+ result = IssuerContract.new.call(params)
46
+ unless result.success?
47
+ raise ContractError.new(result.errors.to_h)
48
+ end
49
+ end
50
+
51
+
52
+ end
53
+ end
@@ -0,0 +1,49 @@
1
+
2
+
3
+
4
+ module FreshJwt
5
+
6
+ module Entity
7
+ module Callable
8
+ def call(*args)
9
+ new(*args)
10
+ end
11
+
12
+ alias [] call
13
+ end
14
+ end
15
+
16
+ class Payload
17
+ extend Dry::Initializer
18
+ extend Entity::Callable
19
+
20
+ option :jti, default: proc { SecureRandom.hex }
21
+ option :iat, proc(&:to_i), default: proc { Time.now }
22
+ option :exp, ->(val){ (Time.now + val).to_i }, default: -> { 10 * 60 } #default: ->(val) { iat + 10*60 }
23
+ option :extend, proc(&:to_h), default: proc{ {} }
24
+
25
+ def default_payload
26
+ @default_payload ||= self.class.dry_initializer.attributes(self).slice(:jti, :iat, :exp)
27
+ end
28
+
29
+ def to_hash
30
+ @params ||= default_payload.merge(extend)
31
+ end
32
+
33
+ end
34
+ end
35
+
36
+ =begin
37
+ def registered_claim(sym)
38
+ case sym
39
+ when :aud then Claim::Aud
40
+ when :exp then Claim::Exp
41
+ when :iat then Claim::Iat
42
+ when :iss then Claim::Iss
43
+ when :jti then Claim::Jti
44
+ when :nbf then Claim::Nbf
45
+ when :sub then Claim::Sub
46
+ else nil # custom claim
47
+ end
48
+ end
49
+ =end
@@ -0,0 +1,49 @@
1
+ module FreshJwt
2
+ class Store
3
+ extend Dry::Monads[:result]
4
+ @@store = []
5
+ #{
6
+ # "token_value": {
7
+ # jti: 1,
8
+ # exp: 2,
9
+ # type: :access
10
+ # }
11
+ #}
12
+
13
+ def self.save token
14
+
15
+ @@store << token
16
+ end
17
+ def self.get token
18
+ @@store.find{ |t| t == token }
19
+ end
20
+ def self.all
21
+ @@store
22
+ end
23
+
24
+ def self.find_by_token token
25
+ @@store.find{ |t| t.token == token }
26
+ end
27
+
28
+ def self.single_transaction token
29
+ begin
30
+ save token
31
+ return Success()
32
+ rescue Exception => error
33
+ #puts error
34
+ return Failure(error: error.message)
35
+ end
36
+ end
37
+
38
+ def self.transaction &block
39
+ begin
40
+ block.call
41
+ return Success()
42
+ rescue Exception => error
43
+
44
+ puts error
45
+ return Failure(error: error.message)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,43 @@
1
+ module FreshJwt
2
+ class Validator
3
+ extend Dry::Initializer
4
+ include Dry::Monads[:result, :do]
5
+
6
+ option :algorithm, default: -> { 'HS256' }
7
+ option :secret, default: -> { 'SECRET' }
8
+ option :token_repo, default: -> { Store }
9
+
10
+ def call(token)
11
+ yield valid_by_store? token
12
+ payload, _ = yield decode token
13
+ valid_payload? payload
14
+ end
15
+
16
+ def valid_by_store? token
17
+ #access_token = token_repo.find_by_token(token)
18
+ #if access_token.expired_at > Time.now.to_i
19
+ if true
20
+ return Success()
21
+ else
22
+ return Failure(validator: :invalid_by_store)
23
+ end
24
+ end
25
+
26
+ def decode token
27
+ begin
28
+ result = JWT.decode(token, secret, true, {algorithm: algorithm})
29
+ Success(result)
30
+ rescue JWT::VerificationError, JWT::DecodeError => e
31
+ Failure(decode_error: ["Cannot validate", e.message])
32
+ end
33
+ end
34
+
35
+ def valid_payload? payload
36
+ if payload["exp"] > Time.now.to_i
37
+ Success()
38
+ else
39
+ Failure(valid_payload: false)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module FreshJwt
2
+ VERSION = '0.0.1'
3
+ end
data/main.rb ADDED
@@ -0,0 +1,67 @@
1
+ require_relative 'lib/fresh_jwt'
2
+
3
+ #payload = FreshJwt::Payload.new(extend:{
4
+ # user_id: rand(1000),
5
+ # rating: 99.4
6
+ #})
7
+
8
+ payload = {
9
+ user_id: rand(1000),
10
+ rating: 99.4
11
+ }
12
+
13
+ issuer = FreshJwt::Issuer.new(algorithm: 'HS256', payload:{
14
+ user_id: rand(1000),
15
+ rating: 99.4
16
+ })
17
+
18
+ access_token, refresh_token = begin
19
+ issuer.call
20
+ rescue FreshJwt::ContractError => e
21
+ p e.message
22
+ nil
23
+ end
24
+ token = access_token.token
25
+
26
+ exit(0) unless token
27
+ p token
28
+ p "decoded " << FreshJwt::Decoder.new().decode(token:token).to_s
29
+ #p JWT.decode(token, issuer.secret, 'HS256')
30
+ p "valid? " << FreshJwt::Validator.new().call(token).to_s
31
+ payload = FreshJwt::Payload.new({})
32
+ p "store: #{FreshJwt::Store.all}"
33
+
34
+
35
+ ########################################
36
+ ##### HOW TO COMFORTABLE WORK WITH #####
37
+ ########################################
38
+ =begin
39
+ issuer = FreshJwt::Issuer.new(algorithm: 'HS256')
40
+ #OR
41
+ issuer = FreshJwt::Issuer.new
42
+ access, refresh = issuer.call(payload: {
43
+ user_id: 1
44
+ })
45
+
46
+ authenticator = FreshJwt::Authenticator.new
47
+ authenticator.call(access_token: token)
48
+ # Success or 403 failure with errors describe
49
+
50
+ refresher = FreshJwt::Refresher.new
51
+ access, refresh = refresher.call(
52
+ refresh_token: token,
53
+ payload: {
54
+ user_id: 1
55
+ }
56
+ )
57
+ =end
58
+ #FreshJwt::Revoker
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
@@ -0,0 +1,51 @@
1
+ require 'dry/monads'
2
+ require 'dry/monads/do'
3
+ require 'securerandom'
4
+
5
+ #extend Dry::Monads[:maybe, :result]
6
+ #p None().to_result( {error: :some} )
7
+
8
+ class Refresher
9
+ include Dry::Monads[:result, :do]
10
+
11
+ def initialize(token:)
12
+ @token = token
13
+ end
14
+
15
+ def call
16
+ token = yield validate_token @token
17
+ new_token = yield create_new_token token#.value!
18
+ store_token new_token#.value!
19
+ end
20
+
21
+ def validate_token token
22
+ if true
23
+ Success(token)
24
+ else
25
+ Failure(:token_not_valid)
26
+ end
27
+ end
28
+
29
+ def create_new_token token
30
+ if true
31
+ Success(SecureRandom.hex<< ' ' << token)
32
+ else
33
+ Failure(:cannot_create_token)
34
+ end
35
+ end
36
+
37
+ def store_token token
38
+ if true
39
+ $db << token
40
+ Success(token)
41
+ else
42
+ Failure(:some_errors_when_store)
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ $db = []
49
+ token = Refresher.new(token: '1').call
50
+ #p token.failure?
51
+ p token.value_or{ |err| err }
@@ -0,0 +1,37 @@
1
+ #require 'dry/monads/result'
2
+
3
+ include Dry::Monads[:result]
4
+ RSpec.describe FreshJwt::Issuer do
5
+
6
+ let(:jwt_regexp) { /^[\w-]*\.[\w-]*\.[\w-]*$/ }
7
+ let(:plain_token) { 'Token' }
8
+ let(:secret) { SecureRandom.hex }
9
+ let(:user_id) { rand(1000) }
10
+ let(:payload) { {user_id:user_id} }
11
+ let(:issuer) { described_class.new(payload: payload) }
12
+ before do
13
+ allow(SecureRandom).to receive(:hex).and_return(plain_token)
14
+ end
15
+ it 'should be kind of string' do
16
+ expect(issuer.call).to be_kind_of Array
17
+ end
18
+ it 'return AccessToken as first value' do
19
+ expect(issuer.call.first).to be_kind_of FreshJwt::Entity::AccessToken
20
+ end
21
+ it 'return RefreshToken as first value' do
22
+ expect(issuer.call.last).to be_kind_of FreshJwt::Entity::RefreshToken
23
+ end
24
+ it 'match jwt_regexp' do
25
+ expect(issuer.call.first.token).to match jwt_regexp
26
+ end
27
+ #let(:store) { class_double(FreshJwt::Store)}
28
+ context 'store transaction is failed' do
29
+ before do
30
+ allow(FreshJwt::Store).to receive(:save).and_raise(StandardError)
31
+ end
32
+ it 'return empty array' do
33
+ expect(issuer.call).to be_kind_of(Failure)
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,16 @@
1
+ Struct.new('AccessToken', :token)
2
+
3
+ RSpec.describe FreshJwt::Store do
4
+ let(:token) { SecureRandom.hex }
5
+ let(:unknown_token) { SecureRandom.hex }
6
+ let(:token_obj) { Struct::AccessToken.new(token) }
7
+ before do
8
+ described_class.save token_obj
9
+ end
10
+ it 'should contant token' do
11
+ expect(described_class.find_by_token(token)).to eq(token_obj)
12
+ end
13
+ it 'should not contant token' do
14
+ expect(described_class.find_by_token(unknown_token)).to be_nil
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FreshJwt::Entity::AccessToken do
4
+ let(:token) { SecureRandom.hex }
5
+ let(:access_token) { FreshJwt::Entity::AccessToken.new(token: token) }
6
+ before do
7
+
8
+ end
9
+ it 'create access token klass' do
10
+ expect(access_token.name).to eq('AccessToken')
11
+ end
12
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ include Dry::Monads[:result]
3
+ RSpec.describe FreshJwt::Validator do
4
+ #let(:token) { SecureRandom.hex }
5
+ #let(:access_token) { FreshJwt::Entity::AccessToken.new(token: token) }
6
+ let(:random_token) { SecureRandom.hex }
7
+ before do
8
+ @access_token, @refresh_token = FreshJwt::Issuer.new(payload: {}).call()
9
+ #FreshJwt::Store.save access_token
10
+ end
11
+ it 'can validate coz knwon token' do
12
+ expect(described_class.new.call(@access_token.token)).to eq(Success())
13
+ end
14
+
15
+ it 'con not validate coz unknwon token' do
16
+ expect(described_class.new.call(random_token)).to be_kind_of(Failure)
17
+ end
18
+ context 'too late for validate' do
19
+ before do
20
+ @access_token, @refresh_token = FreshJwt::Issuer.new(payload: {}).call()
21
+ t = Time.at( Time.now.to_i + FreshJwt::Expiration::ACCESS + 1 )
22
+ Timecop.travel(t)
23
+ end
24
+ it 'can not validate coz expire token' do
25
+ expect(described_class.new.call(@access_token.token)).to eq(
26
+ Failure({:decode_error=>["Cannot validate", "Signature has expired"]})
27
+ )
28
+ end
29
+ end
30
+ end
31
+
32
+ # Добавить кейсы с разными фейлами
33
+ # -Failure({:decode_error=>"Signature has expired"})
34
+ # +Failure({:decode_error=>"Signature verification raised"})
@@ -0,0 +1,5 @@
1
+ RSpec.describe FreshJwt do
2
+ it 'has a version number' do
3
+ expect(described_class::VERSION).not_to be_nil
4
+ end
5
+ end
@@ -0,0 +1,103 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
16
+
17
+ require_relative '../lib/fresh_jwt'
18
+ require 'timecop'
19
+ RSpec.configure do |config|
20
+ # rspec-expectations config goes here. You can use an alternate
21
+ # assertion/expectation library such as wrong or the stdlib/minitest
22
+ # assertions if you prefer.
23
+ config.expect_with :rspec do |expectations|
24
+ # This option will default to `true` in RSpec 4. It makes the `description`
25
+ # and `failure_message` of custom matchers include text for helper methods
26
+ # defined using `chain`, e.g.:
27
+ # be_bigger_than(2).and_smaller_than(4).description
28
+ # # => "be bigger than 2 and smaller than 4"
29
+ # ...rather than:
30
+ # # => "be bigger than 2"
31
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
32
+ end
33
+
34
+ # rspec-mocks config goes here. You can use an alternate test double
35
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
36
+ config.mock_with :rspec do |mocks|
37
+ # Prevents you from mocking or stubbing a method that does not exist on
38
+ # a real object. This is generally recommended, and will default to
39
+ # `true` in RSpec 4.
40
+ mocks.verify_partial_doubles = true
41
+ end
42
+
43
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
44
+ # have no way to turn it off -- the option exists only for backwards
45
+ # compatibility in RSpec 3). It causes shared context metadata to be
46
+ # inherited by the metadata hash of host groups and examples, rather than
47
+ # triggering implicit auto-inclusion in groups with matching metadata.
48
+ config.shared_context_metadata_behavior = :apply_to_host_groups
49
+
50
+ # The settings below are suggested to provide a good initial experience
51
+ # with RSpec, but feel free to customize to your heart's content.
52
+ =begin
53
+ # This allows you to limit a spec run to individual examples or groups
54
+ # you care about by tagging them with `:focus` metadata. When nothing
55
+ # is tagged with `:focus`, all examples get run. RSpec also provides
56
+ # aliases for `it`, `describe`, and `context` that include `:focus`
57
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
58
+ config.filter_run_when_matching :focus
59
+
60
+ # Allows RSpec to persist some state between runs in order to support
61
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
62
+ # you configure your source control system to ignore this file.
63
+ config.example_status_persistence_file_path = "spec/examples.txt"
64
+
65
+ # Limits the available syntax to the non-monkey patched syntax that is
66
+ # recommended. For more details, see:
67
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
68
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
69
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
70
+ config.disable_monkey_patching!
71
+
72
+ # This setting enables warnings. It's recommended, but in some cases may
73
+ # be too noisy due to issues in dependencies.
74
+ config.warnings = true
75
+
76
+ # Many RSpec users commonly either run the entire suite or an individual
77
+ # file, and it's useful to allow more verbose output when running an
78
+ # individual spec file.
79
+ if config.files_to_run.one?
80
+ # Use the documentation formatter for detailed output,
81
+ # unless a formatter has already been configured
82
+ # (e.g. via a command-line flag).
83
+ config.default_formatter = "doc"
84
+ end
85
+
86
+ # Print the 10 slowest examples and example groups at the
87
+ # end of the spec run, to help surface which specs are running
88
+ # particularly slow.
89
+ config.profile_examples = 10
90
+
91
+ # Run specs in random order to surface order dependencies. If you find an
92
+ # order dependency and want to debug it, you can fix the order by providing
93
+ # the seed, which is printed after each run.
94
+ # --seed 1234
95
+ config.order = :random
96
+
97
+ # Seed global randomization in this process using the `--seed` CLI option.
98
+ # Setting this allows you to use `--seed` to deterministically reproduce
99
+ # test failures related to randomization by passing the same `--seed` value
100
+ # as the one that triggered the failure.
101
+ Kernel.srand config.seed
102
+ =end
103
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fresh_jwt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrei Mosin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-06-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Issue access JWT & secure with refresh token
14
+ email: otmosina@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - ".gitignore"
20
+ - ".rspec"
21
+ - Gemfile
22
+ - Gemfile.lock
23
+ - README.md
24
+ - bin/console
25
+ - lib/contracts/issuer_contract.rb
26
+ - lib/fresh_jwt.rb
27
+ - lib/fresh_jwt/contract_validator.rb
28
+ - lib/fresh_jwt/entity/access_token.rb
29
+ - lib/fresh_jwt/expiration.rb
30
+ - lib/fresh_jwt/issuer.rb
31
+ - lib/fresh_jwt/payload.rb
32
+ - lib/fresh_jwt/store.rb
33
+ - lib/fresh_jwt/validator.rb
34
+ - lib/fresh_jwt/version.rb
35
+ - main.rb
36
+ - refresher_draft.rb
37
+ - spec/fresh_jwt/issuer_spec.rb
38
+ - spec/fresh_jwt/store_spec.rb
39
+ - spec/fresh_jwt/token_spec.rb
40
+ - spec/fresh_jwt/validator_spec.rb
41
+ - spec/fresh_jwt_spec.rb
42
+ - spec/spec_helper.rb
43
+ homepage: https://rubygems.org/gems/hola
44
+ licenses:
45
+ - MIT
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.1.6
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Fresh JWT token!
66
+ test_files:
67
+ - spec/fresh_jwt/issuer_spec.rb
68
+ - spec/fresh_jwt/store_spec.rb
69
+ - spec/fresh_jwt/token_spec.rb
70
+ - spec/fresh_jwt/validator_spec.rb
71
+ - spec/fresh_jwt_spec.rb
72
+ - spec/spec_helper.rb