jwt_sessions 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
+ SHA1:
3
+ metadata.gz: 4b639d9a9560b309205c8cc09a55d4e29ade5af7
4
+ data.tar.gz: 4936d31724cfe88af332f0edf5c29faefdd8ed74
5
+ SHA512:
6
+ metadata.gz: 54ba0f9e9826cebeba55de7687e5215255a827e3765b360913cf460569d5f121657a9d0b6ecdab72149403ad2d2c15e594e55a508516b4bb9f1dd10c31568727
7
+ data.tar.gz: 0d91952a603ef98df693c9daa3603c52523a7fa045a9a6c7d9ba90c9a15c21706305a657ee0870a74977ffd51ea3596b2fff9148b74313eb7916b7f755e5a997
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ jwt_sessions (0.0.0)
5
+ jwt (~> 1.4)
6
+ redis (~> 3)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ coderay (1.1.2)
12
+ jwt (1.5.6)
13
+ method_source (0.9.0)
14
+ pry (0.11.3)
15
+ coderay (~> 1.1.0)
16
+ method_source (~> 0.9.0)
17
+ redis (3.3.5)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ jwt_sessions!
24
+ pry
25
+
26
+ BUNDLED WITH
27
+ 1.16.1
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Yulia Oletskaya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # jwt_sessions
2
+ XSS/CSRF safe JWT auth designed for SPA
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |task|
7
+ task.test_files = FileList['test/units/test_*.rb', 'test/units/**/test_*.rb']
8
+ end
9
+ desc 'Run tests'
10
+
11
+ task default: :test
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt_sessions/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'jwt_sessions'
7
+ s.version = JWTSessions::VERSION
8
+ s.date = '2018-03-08'
9
+ s.summary = 'JWT Sessions'
10
+ s.description = 'XSS/CSRF safe JWT auth designed for SPA'
11
+ s.authors = ['Yulia Oletskaya']
12
+ s.email = 'yulia.oletskaya@gmail.com'
13
+ s.homepage = 'http://rubygems.org/gems/jwt_sessions'
14
+ s.license = 'MIT'
15
+
16
+ s.files = Dir['*', 'lib/**/*', 'LICENSE', 'README.md']
17
+ s.test_files = Dir['test/units/*', 'test/units/**/*']
18
+ s.require_paths = ['lib']
19
+
20
+ s.add_dependency 'jwt', '~>1.4'
21
+ s.add_dependency 'redis', '~>3'
22
+
23
+ s.add_development_dependency 'pry', '~>0.11'
24
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ require 'jwt_sessions/errors'
6
+ require 'jwt_sessions/token'
7
+ require 'jwt_sessions/redis_token_store'
8
+ require 'jwt_sessions/refresh_token'
9
+ require 'jwt_sessions/csrf_token'
10
+ require 'jwt_sessions/access_token'
11
+ require 'jwt_sessions/session'
12
+ require 'jwt_sessions/authorization'
13
+ require 'jwt_sessions/version'
14
+
15
+ module JWTSessions
16
+ extend self
17
+
18
+ attr_writer :token_store
19
+
20
+ DEFAULT_SETTINGS_KEYS = %i[redis_host
21
+ redis_port
22
+ redis_db_name
23
+ token_prefix
24
+ algorithm
25
+ exp_time
26
+ refresh_exp_time].freeze
27
+ DEFAULT_REDIS_HOST = '127.0.0.1'
28
+ DEFAULT_REDIS_PORT = '6379'
29
+ DEFAULT_REDIS_DB_NAME = 'jwtokens'
30
+ DEFAULT_TOKEN_PREFIX = 'jwt_'
31
+ DEFAULT_ALGORITHM = 'HS256'
32
+ DEFAULT_EXP_TIME = 3600 # 1 hour in seconds
33
+ DEFAULT_REFRESH_EXP_TIME = 604800 # 1 week in seconds
34
+
35
+
36
+ DEFAULT_SETTINGS_KEYS.each do |setting|
37
+ var_name = :"@#{setting}"
38
+
39
+ define_method(setting) do
40
+ if instance_variables.include?(var_name)
41
+ instance_variable_get(var_name)
42
+ else
43
+ instance_variable_set(var_name,
44
+ const_get("DEFAULT_#{setting.upcase}"))
45
+ end
46
+ end
47
+
48
+ define_method("#{setting}=") do |val|
49
+ instance_variable_set(var_name, val)
50
+ end
51
+ end
52
+
53
+ def token_store
54
+ RedisTokenStore.instance(redis_host, redis_port, redis_db_name, token_prefix)
55
+ end
56
+
57
+ def encryption_key
58
+ raise Errors::Malconfigured, 'encryption_key is not specified' unless @encryption_key
59
+ @encryption_key
60
+ end
61
+
62
+ def access_expiration
63
+ Time.now.to_i + exp_time.to_i
64
+ end
65
+
66
+ def refresh_expiration
67
+ Time.now.to_i + refresh_exp_time.to_i
68
+ end
69
+
70
+ def encryption_key=(key)
71
+ @encryption_key = key
72
+ end
73
+ end
@@ -0,0 +1,29 @@
1
+ module JWTSessions
2
+ class AccessToken
3
+ attr_reader :token, :payload, :uid, :expiration, :csrf
4
+
5
+ def initialize(csrf, payload, store, uid = SecureRandom.uuid, expiration = JWTSessions.access_expiration)
6
+ @csrf = csrf
7
+ @uid = uid
8
+ @expiration = expiration
9
+ @payload = payload
10
+ @token = Token.encode(payload.merge(uid: uid, exp: expiration.to_i))
11
+ end
12
+
13
+ def destroy
14
+ store.destroy_access(uid)
15
+ end
16
+
17
+ class << self
18
+ def create(csrf, payload, store)
19
+ new(csrf, payload, store).tap do |inst|
20
+ store.persist_access(inst.uid, inst.csrf, inst.expiration)
21
+ end
22
+ end
23
+
24
+ def destroy(uid, store)
25
+ store.destroy_access(uid)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWTSessions
4
+ module Authorization
5
+ CSRF_SAFE_METHODS = ['GET', 'HEAD'].freeze
6
+
7
+ protected
8
+
9
+ def authenticate_request!
10
+ begin
11
+ cookieless_auth
12
+ rescue Errors::Unauthorized
13
+ cookie_based_auth
14
+ end
15
+
16
+ invalid_authentication unless Token.valid_payload?(payload)
17
+ check_csrf
18
+ end
19
+
20
+ def invalid_authentication
21
+ raise Errors::Unauthorized
22
+ end
23
+
24
+ def get_from_payload(key)
25
+ payload[key]
26
+ end
27
+
28
+ def check_csrf
29
+ invalid_authentication if should_check_csrf? && @_csrf_check && !valid_csrf_token?(retrieve_csrf)
30
+ end
31
+
32
+ def should_check_csrf?
33
+ !CSRF_SAFE_METHODS.include?(request_method)
34
+ end
35
+
36
+ def token_header
37
+ raise Errors::Malconfigured, 'token_header is not implemented'
38
+ end
39
+
40
+ def token_cookie
41
+ raise Errors::Malconfigured, 'token_cookie is not implemented'
42
+ end
43
+
44
+ def csrf_header
45
+ raise Errors::Malconfigured, 'csrf_header is not implemented'
46
+ end
47
+
48
+ def request_headers
49
+ raise Errors::Malconfigured, 'request_headers is not implemented'
50
+ end
51
+
52
+ def request_cookies
53
+ raise Errors::Malconfigured, 'request_cookies is not implemented'
54
+ end
55
+
56
+ def request_method
57
+ raise Errors::Malconfigured, 'request_method is not implemented'
58
+ end
59
+
60
+ def valid_csrf_token?(csrf_token)
61
+ JWTSessions::Session.new.valid_csrf?(@_raw_token, csrf_token)
62
+ end
63
+
64
+ def cookieless_auth
65
+ @_csrf_check = false
66
+ @_raw_token = token_from_headers
67
+ end
68
+
69
+ def cookie_based_auth
70
+ @_csrf_check = true
71
+ @_raw_token = token_from_cookies
72
+ end
73
+
74
+ private
75
+
76
+ def retrieve_csrf
77
+ token = requset_headers[csrf_header]
78
+ raise Errors::Unauthorized, 'CSRF token is not found' unless token
79
+ token
80
+ end
81
+
82
+ def token_from_headers
83
+ raw_token = request_headers[token_header]
84
+ token = raw_token.split(' ')[-1]
85
+ raise Errors::Unauthorized, 'Token is not found among request headers' unless token
86
+ token
87
+ end
88
+
89
+ def token_from_cookies
90
+ token = request_cookies[token_cookie]
91
+ raise Errors::Unauthorized, 'Token is not found among cookies' unless token
92
+ token
93
+ end
94
+
95
+ def payload
96
+ @_payload ||= Token.decode(@token).first
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWTSessions
4
+ class CSRFToken
5
+ CSRF_LENGTH = 32
6
+
7
+ attr_reader :encoded, :token
8
+
9
+ def initialize(csrf_token = nil)
10
+ @encoded = csrf_token || SecureRandom.base64(CSRF_LENGTH)
11
+ @token = masked_token
12
+ end
13
+
14
+ def valid_authenticity_token?(encoded_masked_token)
15
+ if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
16
+ return false
17
+ end
18
+
19
+ begin
20
+ masked_token = Base64.strict_decode64(encoded_masked_token)
21
+ rescue ArgumentError
22
+ return false
23
+ end
24
+
25
+ if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
26
+ secure_compare(masked_token, raw_token)
27
+ elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
28
+ csrf_token = unmask_token(masked_token)
29
+ secure_compare(csrf_token, raw_token)
30
+ else
31
+ false
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def secure_compare(a, b)
38
+ return false unless a.bytesize == b.bytesize
39
+
40
+ l = a.unpack "C#{a.bytesize}"
41
+
42
+ res = 0
43
+ b.each_byte { |byte| res |= byte ^ l.shift }
44
+ res == 0
45
+ end
46
+
47
+ def unmask_token(masked_token)
48
+ one_time_pad = masked_token[0...CSRF_LENGTH]
49
+ encrypted_csrf_token = masked_token[CSRF_LENGTH..-1]
50
+ xor_byte_strings(one_time_pad, encrypted_csrf_token)
51
+ end
52
+
53
+ def masked_token
54
+ one_time_pad = SecureRandom.random_bytes(CSRF_LENGTH)
55
+ encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
56
+ masked_token = one_time_pad + encrypted_csrf_token
57
+ Base64.strict_encode64(masked_token)
58
+ end
59
+
60
+ def raw_token
61
+ Base64.strict_decode64(encoded)
62
+ end
63
+
64
+ def xor_byte_strings(s1, s2)
65
+ s2_bytes = s2.bytes
66
+ s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 }
67
+ s2_bytes.pack("C*")
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,8 @@
1
+ module JWTSessions
2
+ module Errors
3
+ class Error < RuntimeError; end
4
+ class Malconfigured < Error; end
5
+ class InvalidPayload < Error; end
6
+ class Unauthorized < Error; end
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ module JWTSessions
2
+ module RailsAuthorization
3
+ include Authorization
4
+
5
+ def request_headers
6
+ request.headers
7
+ end
8
+
9
+ def request_cookies
10
+ request.cookie_jar
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'redis'
4
+
5
+ module JWTSessions
6
+ class RedisTokenStore
7
+ class << self
8
+ def instance(redis_host, redis_port, redis_db_name, prefix)
9
+ @_tokens_store ||= Redis.new(url: "redis://#{redis_host}:#{redis_port}/#{redis_db_name}")
10
+ @_token_prefix ||= prefix
11
+
12
+ new(@_tokens_store, @_token_prefix)
13
+ end
14
+
15
+ def clear
16
+ @_tokens_store = nil
17
+ @_token_prefix = nil
18
+ end
19
+
20
+ private
21
+
22
+ def new(store, prefix)
23
+ super(store, prefix)
24
+ end
25
+ end
26
+
27
+ attr_reader :store, :prefix
28
+
29
+ def initialize(store, prefix)
30
+ @store = store
31
+ @prefix = prefix
32
+ end
33
+
34
+ def fetch_access(uid)
35
+ csrf = store.get(access_key(uid))
36
+ return {} if csrf.nil?
37
+ { csrf: csrf }
38
+ end
39
+
40
+ def persist_access(uid, csrf, expiration)
41
+ key = access_key(uid)
42
+ store.set(key, csrf)
43
+ store.expireat(key, expiration)
44
+ end
45
+
46
+ def fetch_refresh(uid)
47
+ keys = [:csrf, :access_uid, :access_expiration, :expiration]
48
+ values = store.hmget(refresh_key(uid), *keys)
49
+ return {} if values.empty?
50
+ keys.each_with_index.inject({}) { |acc, (key, index)| acc[key] = values[index]; acc }
51
+ end
52
+
53
+ def persist_refresh(uid, access_expiration, access_uid, csrf, expiration)
54
+ key = refresh_key(uid)
55
+ update_refresh(uid, access_expiration, access_uid, csrf)
56
+ store.hset(key, :expiration, expiration)
57
+ store.expireat(key, expiration)
58
+ end
59
+
60
+ def update_refresh(uid, access_expiration, access_uid, csrf)
61
+ store.hmset(refresh_key(uid), :csrf, csrf, :access_expiration, access_expiration, :access_uid, access_uid)
62
+ end
63
+
64
+ def destroy_refresh(uid)
65
+ store.del(refresh_key(uid))
66
+ end
67
+
68
+ def destroy_access(uid)
69
+ store.del(access_key(uid))
70
+ end
71
+
72
+ private
73
+
74
+ def access_key(uid)
75
+ "#{prefix}_access_#{uid}"
76
+ end
77
+
78
+ def refresh_key(uid)
79
+ "#{prefix}_refresh_#{uid}"
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWTSessions
4
+ class RefreshToken
5
+ attr_reader :expiration, :uid, :token, :csrf, :access_uid, :access_expiration, :store
6
+
7
+ def initialize(csrf, access_uid, access_expiration, store, uid = SecureRandom.uuid, expiration = JWTSessions.refresh_expiration)
8
+ @csrf = csrf
9
+ @access_uid = access_uid
10
+ @access_expiration = access_expiration
11
+ @uid = uid
12
+ @expiration = expiration
13
+ @store = store
14
+ @token = Token.encode(uid: uid, exp: expiration.to_i)
15
+ end
16
+
17
+ class << self
18
+ def create(csrf, access_uid, access_expiration, store)
19
+ inst = new(csrf, access_uid, access_expiration, store)
20
+ inst.send(:persist_in_store)
21
+ inst
22
+ end
23
+
24
+ def find(uid, store)
25
+ token_attrs = store.fetch_refresh(uid)
26
+ raise Errors::Unauthorized, 'Refresh token not found' if token_attrs.empty?
27
+ new(token_attrs[:csrf],
28
+ token_attrs[:access_uid],
29
+ token_attrs[:access_expiration],
30
+ store,
31
+ uid,
32
+ token_attrs[:expiration])
33
+ end
34
+
35
+ def destroy(uid, store)
36
+ store.destroy_refresh(uid)
37
+ end
38
+ end
39
+
40
+ def update(access_uid, access_expiration, csrf)
41
+ store.update_refresh(uid, access_uid, access_expiration, csrf)
42
+ end
43
+
44
+ def destroy
45
+ store.destroy_refresh(uid)
46
+ end
47
+
48
+ private
49
+
50
+ def persist_in_store
51
+ store.persist_refresh(uid, access_expiration, access_uid, csrf, expiration)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWTSessions
4
+ class Session
5
+ attr_reader :access_token, :refresh_token, :csrf_token
6
+ attr_accessor :payload, :store
7
+
8
+ def initialize(options = {})
9
+ @store = options.fetch(:store, JWTSessions.token_store)
10
+ @payload = options.fetch(:payload, {})
11
+ end
12
+
13
+ def login
14
+ create_csrf_token
15
+ create_access_token
16
+ create_refresh_token
17
+
18
+ tokens_hash
19
+ end
20
+
21
+ def valid_csrf?(access_token, csrf_token)
22
+ csrf(access_token).valid_authenticity_token?(csrf_token)
23
+ end
24
+
25
+ def masked_csrf(access_token)
26
+ csrf(access_token).token
27
+ end
28
+
29
+ def refresh(refresh_token, &block)
30
+ refresh_token_data(refresh_token)
31
+ refresh_by_uid(&block)
32
+ end
33
+
34
+ private
35
+
36
+ def refresh_by_uid(&block)
37
+ check_refresh_on_time(&block) if block_given?
38
+ AccessToken.destroy(@_refresh.access_uid, store)
39
+ issue_tokens_after_refresh
40
+ end
41
+
42
+ def csrf(access_token)
43
+ token_data = access_token_data(access_token)
44
+ raise Errors::Unauthorized, 'Access token not found' if token_data.empty?
45
+ CSRFToken.new(token_data[:csrf])
46
+ end
47
+
48
+ def access_token_data(token)
49
+ uid = token_uid(token, :access)
50
+ store.fetch_access(uid)
51
+ end
52
+
53
+ def refresh_token_data(token)
54
+ uid = token_uid(token, :refresh)
55
+ retrieve_refresh_token(uid)
56
+ end
57
+
58
+ def token_uid(token, type)
59
+ token_payload = JWTSessions::Token.decode(refresh_token).first
60
+ uid = token_payload.fetch('uid', nil)
61
+ if uid.nil?
62
+ message = "#{type.to_s.capitalize} token payload does not contain token uid"
63
+ raise Errors::InvalidPayload, message
64
+ end
65
+ uid
66
+ end
67
+
68
+ def retrieve_refresh_token(uid)
69
+ @_refresh = RefreshToken.find(uid, store)
70
+ end
71
+
72
+ def tokens_hash
73
+ { csrf: csrf_token, access: access_token, refresh: refresh_token }
74
+ end
75
+
76
+ def check_refresh_on_time
77
+ expiration = @_refresh.access_expiration
78
+ yield @_refresh.uid, expiration if expiration.to_i > Time.now.to_i
79
+ end
80
+
81
+ def issue_tokens_after_refresh
82
+ create_csrf_token
83
+ create_access_token
84
+ update_refresh_token
85
+
86
+ tokens_hash
87
+ end
88
+
89
+ def update_refresh_token
90
+ @_refresh.update(@_access.uid, @_access.expiration, @_csrf.encoded)
91
+ @refresh_token = @_refresh.token
92
+ end
93
+
94
+ def create_csrf_token
95
+ @_csrf = CSRFToken.new
96
+ @csrf_token = @_csrf.token
97
+ end
98
+
99
+ def create_refresh_token
100
+ @_refresh = RefreshToken.create(@_csrf.encoded, @_access.uid, @_access.expiration, store)
101
+ @refresh_token = @_refresh.token
102
+ end
103
+
104
+ def create_access_token
105
+ @_access = AccessToken.create(@_csrf.encoded, payload, store)
106
+ @access_token = @_access.token
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt'
4
+
5
+ module JWTSessions
6
+ class Token
7
+ class << self
8
+ def encode(payload)
9
+ exp_payload = meta.merge(payload)
10
+ JWT.encode(exp_payload, JWTSessions.encryption_key, JWTSessions.algorithm)
11
+ end
12
+
13
+ def decode(token)
14
+ JWT.decode(token, JWTSessions.encryption_key, true, { algorithm: JWTSessions.algorithm, verify_expiration: false })
15
+ rescue JWT::DecodeError => e
16
+ raise Errors::Unauthorized, e.message
17
+ rescue StandardError
18
+ raise Errors::Unauthorized, 'could not decode a token'
19
+ end
20
+
21
+ def valid_payload?(payload)
22
+ !expired?(payload)
23
+ end
24
+
25
+ def meta
26
+ { exp: JWTSessions.access_expiration }
27
+ end
28
+
29
+ def expired?(payload)
30
+ Time.at(payload['exp']) < Time.now
31
+ rescue StandardError
32
+ raise Errors::Unauthorized, 'invalid payload expiration time'
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWTSessions
4
+ VERSION = '0.0.1'
5
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'jwt_sessions'
5
+
6
+ class TestSession < Minitest::Test
7
+ attr_accessor :session, :payload, :tokens
8
+ EXPECTED_KEYS = [:access, :csrf, :refresh].freeze
9
+
10
+ def setup
11
+ JWTSessions.encryption_key = '65994c7b523a3232e7aba54d8cbf'
12
+ @payload = { test: 'test' }
13
+ @session = JWTSessions::Session.new(payload: payload)
14
+ @tokens = session.login
15
+ end
16
+
17
+ def test_login
18
+ decoded_access = JWTSessions::Token.decode(tokens[:access]).first
19
+ assert_equal EXPECTED_KEYS, tokens.keys.sort
20
+ assert_equal payload[:test], decoded_access['test']
21
+ end
22
+
23
+ def test_refresh
24
+ refreshed_tokens = session.refresh(tokens[:refresh])
25
+ decoded_access = JWTSessions::Token.decode(refreshed_tokens[:access]).first
26
+ assert_equal EXPECTED_KEYS, refreshed_tokens.keys.sort
27
+ assert_equal payload[:test], decoded_access['test']
28
+ end
29
+
30
+ def test_refresh_with_block_not_expired
31
+ assert_raises JWTSessions::Errors::Unauthorized do
32
+ session.refresh(tokens[:refresh]) do
33
+ raise JWTSessions::Errors::Unauthorized
34
+ end
35
+ end
36
+ end
37
+
38
+ def test_refresh_with_block_expired
39
+ JWTSessions.exp_time = 0
40
+ @session = JWTSessions::Session.new(payload: payload)
41
+ @tokens = session.login
42
+ refreshed_tokens = session.refresh(tokens[:refresh]) do
43
+ raise JWTSessions::Errors::Unauthorized
44
+ end
45
+ JWTSessions.exp_time = 3600
46
+ decoded_access = JWTSessions::Token.decode(refreshed_tokens[:access]).first
47
+ assert_equal EXPECTED_KEYS, refreshed_tokens.keys.sort
48
+ assert_equal payload[:test], decoded_access['test']
49
+ end
50
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'jwt_sessions'
5
+
6
+ class TestToken < Minitest::Test
7
+ attr_accessor :payload
8
+
9
+ def setup
10
+ JWTSessions.encryption_key = '65994c7b523a3232e7aba54d8cbf'
11
+ @payload = { 'user_id' => 1 }
12
+ end
13
+
14
+ def test_valid_token_decode
15
+ token = JWTSessions::Token.encode(payload)
16
+ assert_equal payload['user_id'], JWTSessions::Token.decode(token).first['user_id']
17
+ end
18
+
19
+ def test_invalid_token_decode
20
+ assert_raises JWTSessions::Errors::Unauthorized do
21
+ JWTSessions::Token.decode('abc')
22
+ end
23
+ assert_raises JWTSessions::Errors::Unauthorized do
24
+ JWTSessions::Token.decode('')
25
+ end
26
+ assert_raises JWTSessions::Errors::Unauthorized do
27
+ JWTSessions::Token.decode(nil)
28
+ end
29
+ end
30
+
31
+ def test_payload_exp_time
32
+ assert_raises JWTSessions::Errors::Unauthorized do
33
+ JWTSessions::Token.valid_payload?(payload)
34
+ end
35
+ payload['exp'] = Time.now - (3600 * 24)
36
+ assert_equal false, JWTSessions::Token.valid_payload?(payload)
37
+ payload['exp'] = Time.now + (3600 * 24)
38
+ assert_equal true, JWTSessions::Token.valid_payload?(payload)
39
+ end
40
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'jwt_sessions'
5
+
6
+ class TestJWTSessions < Minitest::Test
7
+ def test_default_settings
8
+ assert_equal JWTSessions::DEFAULT_REDIS_HOST, JWTSessions.redis_host
9
+ assert_equal JWTSessions::DEFAULT_REDIS_DB_NAME, JWTSessions.redis_db_name
10
+ assert_equal JWTSessions::DEFAULT_TOKEN_PREFIX, JWTSessions.token_prefix
11
+ assert_equal JWTSessions::DEFAULT_ALGORITHM, JWTSessions.algorithm
12
+ assert_equal JWTSessions::DEFAULT_EXP_TIME, JWTSessions.exp_time
13
+ assert_equal JWTSessions::DEFAULT_REFRESH_EXP_TIME, JWTSessions.refresh_exp_time
14
+ end
15
+
16
+ def test_encryption_key
17
+ JWTSessions.encryption_key = nil
18
+ assert_raises JWTSessions::Errors::Malconfigured do
19
+ JWTSessions.encryption_key
20
+ end
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jwt_sessions
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Yulia Oletskaya
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-03-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jwt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.11'
55
+ description: XSS/CSRF safe JWT auth designed for SPA
56
+ email: yulia.oletskaya@gmail.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - Gemfile
62
+ - Gemfile.lock
63
+ - LICENSE
64
+ - README.md
65
+ - Rakefile
66
+ - jwt_sessions.gemspec
67
+ - lib/jwt_sessions.rb
68
+ - lib/jwt_sessions/access_token.rb
69
+ - lib/jwt_sessions/authorization.rb
70
+ - lib/jwt_sessions/csrf_token.rb
71
+ - lib/jwt_sessions/errors.rb
72
+ - lib/jwt_sessions/rails_authorization.rb
73
+ - lib/jwt_sessions/redis_token_store.rb
74
+ - lib/jwt_sessions/refresh_token.rb
75
+ - lib/jwt_sessions/session.rb
76
+ - lib/jwt_sessions/token.rb
77
+ - lib/jwt_sessions/version.rb
78
+ - test/units/jwt_sessions/test_session.rb
79
+ - test/units/jwt_sessions/test_token.rb
80
+ - test/units/test_jwt_sessions.rb
81
+ homepage: http://rubygems.org/gems/jwt_sessions
82
+ licenses:
83
+ - MIT
84
+ metadata: {}
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 2.6.13
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: JWT Sessions
105
+ test_files:
106
+ - test/units/test_jwt_sessions.rb
107
+ - test/units/jwt_sessions/test_session.rb
108
+ - test/units/jwt_sessions/test_token.rb