summaryrefslogtreecommitdiff
path: root/spec/lib/json_web_token/hmac_token_spec.rb
blob: f2cbc38196795c10e86dd7a105e5a4f2d99eb5d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# frozen_string_literal: true

require 'json'
require 'timecop'

describe JSONWebToken::HMACToken do
  let(:secret) { 'shh secret squirrel' }

  shared_examples 'a valid, non-expired token' do
    it 'is an Array with two elements' do
      expect(decoded_token).to be_a(Array)
      expect(decoded_token.count).to eq(2)
    end

    it 'contains the following keys in the first Array element Hash - jti, iat, nbf, exp' do
      expect(decoded_token[0].keys).to include('jti', 'iat', 'nbf', 'exp')
    end

    it 'contains the following keys in the second Array element Hash - typ and alg' do
      expect(decoded_token[1]['typ']).to eql('JWT')
      expect(decoded_token[1]['alg']).to eql('HS256')
    end
  end

  describe '.decode' do
    let(:leeway) { described_class::IAT_LEEWAY }
    let(:decoded_token) { described_class.decode(encoded_token, secret, leeway: leeway) }

    context 'with an invalid token' do
      context 'that is junk' do
        let(:encoded_token) { 'junk' }

        it "raises exception saying 'Not enough or too many segments'" do
          expect { decoded_token }.to raise_error(JWT::DecodeError, 'Not enough or too many segments')
        end
      end

      context 'that has been fiddled with' do
        let(:encoded_token) do
          described_class.new(secret).encoded.tap { |token| token[0] = 'E' }
        end

        it "raises exception saying 'Invalid segment encoding'" do
          expect { decoded_token }.to raise_error(JWT::DecodeError, 'Invalid segment encoding')
        end
      end

      context 'that was generated using a different secret' do
        let(:encoded_token) { described_class.new('some other secret').encoded }

        it "raises exception saying 'Signature verification raised" do
          expect { decoded_token }.to raise_error(JWT::VerificationError, 'Signature verification raised')
        end
      end

      context 'that is expired' do
        # Needs the ! so Timecop.freeze() is effective
        let!(:encoded_token) { described_class.new(secret).encoded }

        it "raises exception saying 'Signature has expired'" do
          # Needs to be 120 seconds, because the default expiry is 60 seconds
          # with an additional 60 second leeway.
          Timecop.freeze(Time.now + 120) do
            expect { decoded_token }.to raise_error(JWT::ExpiredSignature, 'Signature has expired')
          end
        end
      end
    end

    context 'with a valid token' do
      let(:encoded_token) do
        hmac_token = described_class.new(secret)
        hmac_token.expire_time = Time.now + expire_time
        hmac_token.encoded
      end

      context 'that has expired' do
        let(:expire_time) { 0 }

        context 'with the default leeway' do
          Timecop.freeze(Time.now + 1) do
            it_behaves_like 'a valid, non-expired token'
          end
        end

        context 'with a leeway of 0 seconds' do
          let(:leeway) { 0 }

          it "raises exception saying 'Signature has expired'" do
            Timecop.freeze(Time.now + 1) do
              expect { decoded_token }.to raise_error(JWT::ExpiredSignature, 'Signature has expired')
            end
          end
        end
      end

      context 'that has not expired' do
        let(:expire_time) { described_class::DEFAULT_EXPIRE_TIME }

        it_behaves_like 'a valid, non-expired token'
      end
    end
  end

  describe '#encoded' do
    let(:decoded_token) { described_class.decode(encoded_token, secret) }

    context 'without data' do
      let(:encoded_token) { described_class.new(secret).encoded }

      it_behaves_like 'a valid, non-expired token'
    end

    context 'with data' do
      let(:data) { { secret_key: 'secret value' }.to_json }
      let(:encoded_token) do
        ec = described_class.new(secret)
        ec[:data] = data
        ec.encoded
      end

      it_behaves_like 'a valid, non-expired token'

      it "contains the 'data' key in the first Array element Hash" do
        expect(decoded_token[0]).to have_key('data')
      end

      it 'can re-read back the data' do
        expect(decoded_token[0]['data']).to eql(data)
      end
    end
  end
end