jwt_keeper 2.0.0

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: bd966e0e79df17e42e2f825289387dcb9f386bde
4
+ data.tar.gz: 32aaa2f138fd3102ea431b3b244fc77e6736bf3a
5
+ SHA512:
6
+ metadata.gz: 842033c9c72f8c350a22c84075d78a8609beb43109bbfa10cb52eaea72034baa99609c862f2a6dc4d6866203c9ef15bfcabd091ef809d4b748fa5ee3519ee6b9
7
+ data.tar.gz: fca8a945722eace48870831e435e6628bd48a666dd7ced51d5066d0bee138b1837b9b838aded1b947464db5d4c73ddd9fa4211b7f39f97f022ae199551d3cde6
data/.editorconfig ADDED
@@ -0,0 +1,14 @@
1
+ # EditorConfig is awesome: http://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ # Unix-style newlines with a newline ending every file
7
+ [*]
8
+ end_of_line = lf
9
+ insert_final_newline = true
10
+ charset = utf-8
11
+
12
+ [*.rb]
13
+ indent_style = space
14
+ indent_size = 2
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ vendor
19
+ .ruby-version
20
+ .env
21
+ /spec/examples.txt
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format Fuubar
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,22 @@
1
+ # inherit_from: .rubocop_todo.yml
2
+ AllCops:
3
+ Include:
4
+ - 'Gemfile'
5
+ - 'Rakefile'
6
+ - '**/*.rake'
7
+ Documentation:
8
+ Enabled: false
9
+ Metrics/LineLength:
10
+ Max: 100
11
+ Style/PercentLiteralDelimiters:
12
+ PreferredDelimiters:
13
+ '%': ()
14
+ '%q': ()
15
+ '%Q': ()
16
+ '%i': []
17
+ '%I': []
18
+ '%r': ()
19
+ '%s': ()
20
+ '%w': []
21
+ '%W': []
22
+ '%x': ()
data/.travis.yml ADDED
@@ -0,0 +1,20 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.8
5
+ - 2.2.4
6
+ - 2.3.0
7
+ - ruby-head
8
+ matrix:
9
+ allow_failures:
10
+ - rvm: ruby-head
11
+ addons:
12
+ code_climate:
13
+ repo_token: f69bb189f348c1d7992d8ed8690d0a2c9c885c1aac45e2f4d48732034592b37b
14
+ services:
15
+ - redis-server
16
+ env:
17
+ global:
18
+ - REDIS_URL=redis://localhost:6379
19
+ notifications:
20
+ email: false
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+
2
+ Copyright (c) 2014 David Rivera
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # JWT Keeper
2
+ [![Build Status](https://img.shields.io/travis/sirwolfgang/jwt_keeper/master.svg)](https://travis-ci.org/sirwolfgang/jwt_keeper)
3
+ [![Dependency Status](https://img.shields.io/gemnasium/sirwolfgang/jwt_keeper.svg)](https://gemnasium.com/sirwolfgang/jwt_keeper)
4
+ [![Code Climate](https://img.shields.io/codeclimate/github/sirwolfgang/jwt_keeper.svg)](https://codeclimate.com/github/sirwolfgang/jwt_keeper)
5
+ [![Test Coverage](https://img.shields.io/codeclimate/coverage/github/sirwolfgang/jwt_keeper.svg)](https://codeclimate.com/github/sirwolfgang/jwt_keeper/coverage)
6
+ [![Inline docs](http://inch-ci.org/github/sirwolfgang/jwt_keeper.svg?style=shields)](http://inch-ci.org/github/sirwolfgang/jwt_keeper)
7
+
8
+ An managing interface layer for handling the creation and validation of JWTs.
9
+
10
+ ## Setup
11
+ - Add `gem 'jwt_keeper', '~> 2.0'` to Gemfile
12
+ - Run `rails generate keeper:install`
13
+ - Configure `config/initializers/keeper.rb`
14
+ - Done
15
+
16
+ ## Basic Usage
17
+ Here are the basic methods you can call to perform various operations
18
+
19
+ ```ruby
20
+ token = JWTKeeper::Token.create(private_claim_hash)
21
+ token = JWTKeeper::Token.find(raw_token_string)
22
+
23
+ token.revoke
24
+ token.rotate
25
+
26
+ token.valid?
27
+ raw_token_string = token.to_jwt
28
+ ```
29
+
30
+ ## Rails Usage
31
+ The designed rails token flow is to receive and respond to requests with the token being present in the `Authorization` part of the header. This is to allow us to seamlessly rotate the tokens on the fly without having to rebuff the request as part of the user flow. Automatic rotation happens as part of the `require_authentication` action, meaning that you will always get the latest token data as
32
+ created by `generate_claims` in your controllers. This new token is added to the response with
33
+ the `respond_with_authentication` action.
34
+
35
+ ```ruby
36
+ class ApplicationController < ActionController::Base
37
+ before_action :require_authentication
38
+ after_action :respond_with_authentication
39
+
40
+ def not_authenticated
41
+ # Overload to return status 401
42
+ end
43
+
44
+ def authenticated(token)
45
+ # Overload to make use of token data
46
+ end
47
+
48
+ def regenerate_claims(old_token)
49
+ # Overload to update claims on automatic rotation.
50
+ current_user = User.find(authentication_token.claims[:uid])
51
+ { uid: current_user.id, usn: current_user.email }
52
+ end
53
+ end
54
+ ```
55
+
56
+ ```ruby
57
+ class SessionsController < ApplicationController
58
+ skip_before_action :require_authentication, only: :create
59
+ skip_after_action :respond_with_authentication, only: :destroy
60
+
61
+ # POST /sessions
62
+ def create
63
+ authentication_token = JWTKeeper::Token.create({ uid: @user.id, usn: @user.email })
64
+ end
65
+
66
+ # PATCH/PUT /sessions
67
+ def update
68
+ authentication_token = request_token.rotate(generate_claims)
69
+ end
70
+
71
+ # DELETE /sessions
72
+ def destroy
73
+ request_token.revoke
74
+ authentication_token = nil
75
+ end
76
+ ```
77
+
78
+ ## Invalidation
79
+ ### Hard Invalidation
80
+ Hard Invalidation is a permanent revocation of the token. The primary cases of this is when a user wishes to logout, or when your security has been otherwise compromised. To revoke all tokens simply update the configuration `secret`. To revoke a single token you can utilize either the class(`Token.revoke(jti)`) or instance(`token.revoke`) method.
81
+
82
+ ### Soft Invalidation
83
+ Soft Invalidation is the process of triggering a rotation upon the next time a token is seen in a request. On the global scale this is done when there is a version mismatch in the config. Utilizing the rails controller flow, this method works even if you have two different versions of your app deployed and requests bounce back and forth; Making rolling deployments and rollbacks completely seamless. To rotate a single token, like in the case of a change of user permissions, simply use the class(`Token.rotate`) method to flag the token for regeneration.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task default: :spec
7
+ task test: :spec
@@ -0,0 +1,4 @@
1
+ redis:
2
+ image: redis:latest
3
+ ports:
4
+ - "6379:6379"
data/example.env ADDED
@@ -0,0 +1 @@
1
+ REDIS_URL=redis://localhost:6379
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'jwt_keeper/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'jwt_keeper'
8
+ spec.version = JWTKeeper::VERSION
9
+ spec.authors = ['David Rivera', 'Zane Wolfgang Pickett']
10
+ spec.email = ['david.r.rivera193@gmail.com', 'sirwolfgang@users.noreply.github.com']
11
+ spec.summary = 'JWT for Rails made easy'
12
+ spec.description = 'It is a keeper'
13
+ spec.homepage = 'https://github.com/sirwolfgang/jwt_keeper'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'yard'
24
+ spec.add_development_dependency 'rubocop'
25
+ spec.add_development_dependency 'dotenv'
26
+
27
+ spec.add_development_dependency 'rspec', '~> 3.4'
28
+ spec.add_development_dependency 'fuubar'
29
+ spec.add_development_dependency 'simplecov'
30
+ spec.add_development_dependency 'codeclimate-test-reporter'
31
+
32
+ spec.add_dependency 'redis', '~> 3.3'
33
+ spec.add_dependency 'rails', '~> 4.2'
34
+ spec.add_dependency 'activesupport', '~> 4.2'
35
+ spec.add_dependency 'jwt', '~> 1.5'
36
+ end
@@ -0,0 +1,15 @@
1
+ require 'rails/generators/base'
2
+
3
+ module JWTKeeper
4
+ class InstallGenerator < Rails::Generators::Base
5
+ source_root File.expand_path('../../../templates', __FILE__)
6
+
7
+ # Copies the default config
8
+ #
9
+ # @example Install
10
+ # rails generate keeper:install
11
+ def copy_files
12
+ copy_file 'jwt_keeper.rb', 'config/initializers/keeper.rb'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ JWTKeeper.configure do |config|
2
+ # The time to expire for the tokens
3
+ # config.expiry = 24.hours
4
+
5
+ # The hashing method to for the tokens
6
+ # Options:
7
+ # HS256 - HMAC using SHA-256 hash algorithm (default)
8
+ # HS384 - HMAC using SHA-384 hash algorithm
9
+ # HS512 - HMAC using SHA-512 hash algorithm
10
+ # RS256 - RSA using SHA-256 hash algorithm
11
+ # RS384 - RSA using SHA-384 hash algorithm
12
+ # RS512 - RSA using SHA-512 hash algorithm
13
+ # ES256 - ECDSA using P-256 and SHA-256
14
+ # ES384 - ECDSA using P-384 and SHA-384
15
+ # ES512 - ECDSA using P-521 and SHA-512
16
+ # config.algorithm = 'HS512'
17
+
18
+ # the secret in which you data is hash with
19
+ # config.secret = 'secret'
20
+
21
+ # the issuer of the tokens
22
+ # config.issuer = 'api.example.com'
23
+
24
+ # the default audience of the tokens
25
+ # config.audience = 'example.com'
26
+
27
+ # the location of redis config file
28
+ # config.redis_connection = Redis.new(connection_options)
29
+
30
+ # A unique idenfitier for the token version.
31
+ # config.version = 1
32
+ end
@@ -0,0 +1,30 @@
1
+ module JWTKeeper
2
+ class Configuration < OpenStruct
3
+ DEFAULTS = {
4
+ algorithm: 'HS512',
5
+ secret: nil,
6
+ expiry: 24.hours,
7
+ issuer: 'api.example.com',
8
+ audience: 'example.com',
9
+ redis_connection: nil,
10
+ version: nil
11
+ }.freeze
12
+
13
+ # Creates a new Configuration from the passed in parameters
14
+ # @param params [Hash] configuration options
15
+ # @return [Configuration]
16
+ def initialize(params = {})
17
+ super(DEFAULTS.merge(params))
18
+ end
19
+
20
+ # @!visibility private
21
+ def base_claims
22
+ {
23
+ iss: JWTKeeper.configuration.issuer, # issuer
24
+ aud: JWTKeeper.configuration.audience, # audience
25
+ exp: JWTKeeper.configuration.expiry.from_now.to_i, # expiration time
26
+ ver: JWTKeeper.configuration.version # Version
27
+ }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,66 @@
1
+ module JWTKeeper
2
+ module Controller
3
+ def self.included(klass)
4
+ klass.class_eval do
5
+ include InstanceMethods
6
+ end
7
+ end
8
+
9
+ module InstanceMethods
10
+ # Available to be used as a before_action by the application's controllers. This is
11
+ # the main logical section for decoding, and automatically rotating tokens
12
+ def require_authentication
13
+ token = authentication_token
14
+ return not_authenticated if token.nil?
15
+
16
+ if token.version_mismatch? || token.pending?
17
+ new_claims = regenerate_claims(token)
18
+ token.rotate(new_claims)
19
+ self.authentication_token = token
20
+ end
21
+
22
+ authenticated(token)
23
+ end
24
+
25
+ # Invoked by the require_authentication method as part of the automatic rotation
26
+ # process. The application should override this method to include the necessary
27
+ # claims.
28
+ def regenerate_claims(old_token)
29
+ end
30
+
31
+ # Moves the authentication_token from the request to the response
32
+ def respond_with_authentication
33
+ response.headers['Authorization'] = request.headers['Authorization']
34
+ end
35
+
36
+ # Decodes and returns the token
37
+ def authentication_token
38
+ return nil unless request.headers['Authorization']
39
+ JWTKeeper::Token.find(request.headers['Authorization'].split.last)
40
+ end
41
+
42
+ # Assigns a token to the request to act as a single source of truth
43
+ def authentication_token=(token)
44
+ request.headers['Authorization'] = "Bearer #{token.to_jwt}"
45
+ end
46
+
47
+ # Used when a user tries to access a page while logged out, is asked to login,
48
+ # and we want to return him back to the page he originally wanted.
49
+ def redirect_back_or_to(url, flash_hash = {})
50
+ redirect_to(session[:return_to_url] || url, flash: flash_hash)
51
+ session[:return_to_url] = nil
52
+ end
53
+
54
+ # The default action for denying non-authenticated connections.
55
+ # You can override this method in your controllers
56
+ def not_authenticated
57
+ redirect_to root_path
58
+ end
59
+
60
+ # The default action for accepting authenticated connections.
61
+ # You can override this method in your controllers
62
+ def authenticated(token)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,39 @@
1
+ module JWTKeeper
2
+ module Datastore
3
+ class << self
4
+ # @!visibility private
5
+ def rotate(jti, seconds)
6
+ set_with_expiry(jti, seconds, :soft)
7
+ end
8
+
9
+ # @!visibility private
10
+ def revoke(jti, seconds)
11
+ set_with_expiry(jti, seconds, :hard)
12
+ end
13
+
14
+ # @!visibility private
15
+ def pending?(jti)
16
+ value = get(jti)
17
+ value.present? && value.to_sym == :soft
18
+ end
19
+
20
+ # @!visibility private
21
+ def revoked?(jti)
22
+ value = get(jti)
23
+ value.present? && value.to_sym == :hard
24
+ end
25
+
26
+ private
27
+
28
+ # @!visibility private
29
+ def set_with_expiry(jti, seconds, type)
30
+ JWTKeeper.configuration.redis_connection.setex(jti, seconds, type)
31
+ end
32
+
33
+ # @!visibility private
34
+ def get(jti)
35
+ JWTKeeper.configuration.redis_connection.get(jti)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,12 @@
1
+ require 'jwt_keeper'
2
+ require 'rails'
3
+
4
+ module JWTKeeper
5
+ # The Sorcery engine takes care of extending ActiveRecord (if used) and ActionController,
6
+ # With the plugin logic.
7
+ class Engine < ::Rails::Engine
8
+ initializer 'extend Controller with keeper' do |_app|
9
+ ActionController::Base.send(:include, JWTKeeper::Controller)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ module JWTKeeper
2
+ # The token is invalid
3
+ class InvalidTokenError < StandardError; end
4
+
5
+ # The token expiry claim is invalid
6
+ class ExpiredTokenError < InvalidTokenError; end
7
+
8
+ # The token was force expired
9
+ class RevokedTokenError < InvalidTokenError; end
10
+
11
+ # The token not before claim is invalid
12
+ class EarlyTokenError < InvalidTokenError; end
13
+
14
+ # The token issuer claim is invalid
15
+ class BadIssuerError < InvalidTokenError; end
16
+
17
+ # The token audience claim is invalid
18
+ class LousyAudienceError < InvalidTokenError; end
19
+ end
@@ -0,0 +1,137 @@
1
+ module JWTKeeper
2
+ class Token
3
+ attr_accessor :claims
4
+
5
+ # Initalizes a new web token
6
+ # @param private_claims [Hash] the custom claims to encode
7
+ def initialize(private_claims = {})
8
+ @claims = {
9
+ nbf: DateTime.now.to_i, # not before
10
+ iat: DateTime.now.to_i, # issued at
11
+ jti: SecureRandom.uuid # JWT ID
12
+ }
13
+ @claims.merge!(JWTKeeper.configuration.base_claims)
14
+ @claims.merge!(private_claims)
15
+ end
16
+
17
+ # Creates a new web token
18
+ # @param private_claims [Hash] the custom claims to encode
19
+ # @return [Token] token object
20
+ def self.create(private_claims)
21
+ new(private_claims)
22
+ end
23
+
24
+ # Decodes and validates an existing token
25
+ # @param raw_token [String] the raw token
26
+ # @return [Token] token object
27
+ def self.find(raw_token)
28
+ claims = decode(raw_token)
29
+ return nil if claims.nil?
30
+
31
+ new_token = new(claims)
32
+ return nil if new_token.revoked?
33
+ new_token
34
+ end
35
+
36
+ # Sets a token to the pending rotation state. The expire is set to the maxium possible time but
37
+ # is inherently ignored by the token's exp check and then rewritten with the revokation on
38
+ # rotate.
39
+ # @param token_jti [String] the token unique id
40
+ def self.rotate(token_jti)
41
+ Datastore.rotate(token_jti, JWTKeeper.configuration.expiry.from_now.to_i)
42
+ end
43
+
44
+ # Revokes a web token
45
+ # @param token_jti [String] the token unique id
46
+ def self.revoke(token_jti)
47
+ Datastore.revoke(token_jti, JWTKeeper.configuration.expiry.from_now.to_i)
48
+ end
49
+
50
+ # Easy interface for using the token's id
51
+ # @return [String] token's uuid
52
+ def id
53
+ claims[:jti]
54
+ end
55
+
56
+ # Revokes and creates a new web token
57
+ # @param new_claims [Hash] Used to override and update claims during rotation
58
+ # @return [String] new token
59
+ def rotate(new_claims = nil)
60
+ revoke
61
+
62
+ new_claims ||= claims.except(:iss, :aud, :exp, :nbf, :iat, :jti)
63
+ new_token = self.class.new(new_claims)
64
+ @claims = new_token.claims
65
+ self
66
+ end
67
+
68
+ # Revokes a web token
69
+ def revoke
70
+ return if invalid?
71
+ Datastore.revoke(id, claims[:exp] - DateTime.now.to_i)
72
+ end
73
+
74
+ # Checks if a web token is pending a rotation
75
+ # @return [Boolean]
76
+ def pending?
77
+ Datastore.pending?(id)
78
+ end
79
+
80
+ # Checks if a web token is pending a global rotation
81
+ # @return [Boolean]
82
+ def version_mismatch?
83
+ claims[:ver] != JWTKeeper.configuration.version
84
+ end
85
+
86
+ # Checks if a web token has been revoked
87
+ # @return [Boolean]
88
+ def revoked?
89
+ Datastore.revoked?(id)
90
+ end
91
+
92
+ # Checks if the token valid?
93
+ # @return [Boolean]
94
+ def valid?
95
+ !invalid?
96
+ end
97
+
98
+ # Checks if the token invalid?
99
+ # @return [Boolean]
100
+ def invalid?
101
+ self.class.decode(encode).nil? || revoked?
102
+ end
103
+
104
+ # Encodes the jwt
105
+ # @return [String]
106
+ def to_jwt
107
+ encode
108
+ end
109
+ alias to_s to_jwt
110
+
111
+ # @!visibility private
112
+ def self.decode(raw_token)
113
+ JWT.decode(raw_token, JWTKeeper.configuration.secret, true,
114
+ algorithm: JWTKeeper.configuration.algorithm,
115
+ verify_iss: true,
116
+ verify_aud: true,
117
+ verify_iat: true,
118
+ verify_sub: false,
119
+ verify_jti: false,
120
+ leeway: 0,
121
+
122
+ iss: JWTKeeper.configuration.issuer,
123
+ aud: JWTKeeper.configuration.audience
124
+ ).first.symbolize_keys
125
+
126
+ rescue JWT::DecodeError
127
+ return nil
128
+ end
129
+
130
+ private
131
+
132
+ # @!visibility private
133
+ def encode
134
+ JWT.encode(claims, JWTKeeper.configuration.secret, JWTKeeper.configuration.algorithm)
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,4 @@
1
+ # Gem Version
2
+ module JWTKeeper
3
+ VERSION = '2.0.0'.freeze
4
+ end
data/lib/jwt_keeper.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'jwt'
2
+ require 'redis'
3
+ require 'active_support'
4
+ require 'active_support/core_ext/numeric'
5
+
6
+ require 'jwt_keeper/version'
7
+ require 'jwt_keeper/exceptions'
8
+ require 'jwt_keeper/configuration'
9
+ require 'jwt_keeper/datastore'
10
+ require 'jwt_keeper/token'
11
+ require 'jwt_keeper/controller'
12
+
13
+ module JWTKeeper
14
+ class << self
15
+ attr_reader :configuration, :datastore
16
+ end
17
+
18
+ # Creates/sets a new configuration for the gem, yield a configuration object
19
+ # @param new_configuration [Configuration] new configuration
20
+ # @return [Configuration] the frozen configuration
21
+ def self.configure(new_configuration = Configuration.new)
22
+ yield(new_configuration) if block_given?
23
+
24
+ @configuration = new_configuration.freeze
25
+ end
26
+
27
+ require 'jwt_keeper/engine' if defined?(Rails)
28
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe JWTKeeper::Configuration do
4
+ it { expect have_constant('DEFAULTS') }
5
+ end