fresh_jwt 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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