diff --git a/.gitignore b/.gitignore index b7e7725..5793629 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,16 @@ -/.bundle/ -/.yardoc -/Gemfile.lock -/_yardoc/ +*.gem /coverage/ -/doc/ /pkg/ /spec/reports/ /tmp/ -*.gem + +/.yardoc/ +/_yardoc/ +/doc/ + +/.bundle/ + +Gemfile.lock + +.idea/ +*.iml diff --git a/.hound.yml b/.hound.yml new file mode 100644 index 0000000..f534df8 --- /dev/null +++ b/.hound.yml @@ -0,0 +1,2 @@ +rubocop: + config_file: .rubocop.yml diff --git a/.rubocop.yml b/.rubocop.yml index 40012d7..7742625 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,13 +1,54 @@ +--- +require: rubocop-rspec + +AllCops: + DefaultFormatter: progress + DisplayCopNames: true + DisplayStyleGuide: false + ExtraDetails: true + TargetRubyVersion: 2.5 + +################################### Bundler #################################### + +################################### Gemspec #################################### + +#################################### Layout #################################### + +##################################### Lint ##################################### + +################################### Metrics #################################### + Metrics/BlockLength: - Exclude: - - spec/**/* + Enabled: false + +Metrics/MethodLength: + Enabled: false + +#################################### Naming #################################### -LineLength: +Naming/FileName: Exclude: - - spec/**/* + - lib/doorkeeper-jwt.rb -StringLiterals: - Enabled: false +################################# Performance ################################## + +#################################### Rails ##################################### + +################################### Security ################################### + +#################################### Style ##################################### Style/Documentation: Enabled: false + +################################### Capybara ################################### + +################################## FactoryBot ################################## + +#################################### RSpec ##################################### + +RSpec/ExampleLength: + Enabled: false + +RSpec/MultipleExpectations: + Enabled: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 76c101a..287bf95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,20 @@ # Change Log All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](http://keepachangelog.com/) -and this project adheres to [Semantic Versioning](http://semver.org/). +The format is based on [Keep a Changelog](http://keepachangelog.com/) and this +project adheres to [Semantic Versioning](http://semver.org/). + +## [0.4.0] - 2018-10-18 + +### Changed + +- Restructured library files to follow naming conventions. (https://guides.rubygems.org/name-your-gem/). ## [0.3.0] - 2018-10-01 ### Added -- Bump JWT gem version. Via [#27](https://github.com/doorkeeper-gem/doorkeeper-jwt/pull/27) by [@pacop](https://github.com/pacop/) +- Bump JWT gem version. Via [#27](https://github.com/doorkeeper-gem/doorkeeper-jwt/pull/27) by [@pacop](https://github.com/pacop/). ## [0.2.1] - 2017-06-07 @@ -20,4 +26,5 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added -- Added support for ["kid" (Key ID) Header Parameter](https://tools.ietf.org/html/rfc7515#section-4.1.4) @travisofthenorth. Allows custom token headers. +- Added support for ["kid" (Key ID) Header Parameter](https://tools.ietf.org/html/rfc7515#section-4.1.4) + @travisofthenorth. Allows custom token headers. diff --git a/Gemfile b/Gemfile index 2f94085..56e7265 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,10 @@ +# frozen_string_literal: true + source 'https://rubygems.org' # Specify your gem's dependencies in doorkeeper-jwt.gemspec gemspec gem 'coveralls', require: false +gem 'rubocop', '~> 0.59.2', require: false +gem 'rubocop-rspec', '~> 1.30', require: false diff --git a/README.md b/README.md index ecb1d75..7c7b51f 100644 --- a/README.md +++ b/README.md @@ -41,16 +41,17 @@ Then add a `Doorkeeper::JWT.configure` block below the `Doorkeeper.configure` bl ```ruby Doorkeeper::JWT.configure do # Set the payload for the JWT token. This should contain unique information - # about the user. - # Defaults to a randomly generated token in a hash - # { token: "RANDOM-TOKEN" } + # about the user. Defaults to a randomly generated token in a hash: + # { token: "RANDOM-TOKEN" } token_payload do |opts| user = User.find(opts[:resource_owner_id]) { iss: 'My App', iat: Time.current.utc.to_i, - jti: SecureRandom.uuid, # @see JWT reserved claims - https://tools.ietf.org/html/draft-jones-json-web-token-07#page-7 + + # @see JWT reserved claims - https://tools.ietf.org/html/draft-jones-json-web-token-07#page-7 + jti: SecureRandom.uuid, user: { id: user.id, @@ -59,47 +60,44 @@ Doorkeeper::JWT.configure do } end - # Optionally set additional headers for the JWT. See https://tools.ietf.org/html/rfc7515#section-4.1 + # Optionally set additional headers for the JWT. See + # https://tools.ietf.org/html/rfc7515#section-4.1 token_headers do |opts| - { - kid: opts[:application][:uid] - } + { kid: opts[:application][:uid] } end - # Use the application secret specified in the Access Grant token - # Defaults to false - # If you specify `use_application_secret true`, both secret_key and secret_key_path will be ignored + # Use the application secret specified in the access grant token. Defaults to + # `false`. If you specify `use_application_secret true`, both `secret_key` and + # `secret_key_path` will be ignored. use_application_secret false # Set the encryption secret. This would be shared with any other applications - # that should be able to read the payload of the token. - # Defaults to "secret" + # that should be able to read the payload of the token. Defaults to "secret". secret_key ENV['JWT_SECRET'] - # If you want to use RS* encoding specify the path to the RSA key - # to use for signing. - # If you specify a secret_key_path it will be used instead of secret_key + # If you want to use RS* encoding specify the path to the RSA key to use for + # signing. If you specify a `secret_key_path` it will be used instead of + # `secret_key`. secret_key_path File.join('path', 'to', 'file.pem') - # Specify encryption type. Supports any algorithm in - # https://github.com/progrium/ruby-jwt - # defaults to nil + # Specify encryption type (https://github.com/progrium/ruby-jwt). Defaults to + # `nil`. encryption_method :hs512 end ``` ## Development -After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive -prompt that will allow you to experiment. +After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt +that will allow you to experiment. -To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update -the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the -version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the +version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git +commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing -1. Fork it ( https://github.com/[my-github-username]/doorkeeper-jwt/fork ) +1. Fork it (https://github.com/[my-github-username]/doorkeeper-jwt/fork) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) diff --git a/Rakefile b/Rakefile index 9722d06..25864e6 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,7 @@ -require "bundler/gem_tasks" -require "rspec/core/rake_task" +# frozen_string_literal: true + +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' RSpec::Core::RakeTask.new diff --git a/bin/console b/bin/console index ca8fd75..8c22cf1 100755 --- a/bin/console +++ b/bin/console @@ -1,14 +1,17 @@ #!/usr/bin/env ruby +# frozen_string_literal: true -require "bundler/setup" -require "doorkeeper-jwt" +require 'bundler/setup' +require 'doorkeeper-jwt' # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) -# require "pry" +# require 'pry' +# # Pry.start -require "irb" +require 'irb' + IRB.start diff --git a/bin/setup b/bin/setup index b65ed50..1f404c2 100755 --- a/bin/setup +++ b/bin/setup @@ -1,7 +1,8 @@ -#!/bin/bash +#!/usr/bin/env bash + set -euo pipefail IFS=$'\n\t' bundle install -# Do any other automated setup that you need to do here +# Do any other automated setup that you need to do here. diff --git a/doorkeeper-jwt.gemspec b/doorkeeper-jwt.gemspec index b613eb7..0fd892d 100644 --- a/doorkeeper-jwt.gemspec +++ b/doorkeeper-jwt.gemspec @@ -1,27 +1,31 @@ +# frozen_string_literal: true + lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'doorkeeper-jwt/version' + +require 'doorkeeper/jwt/version' Gem::Specification.new do |spec| - spec.name = "doorkeeper-jwt" - spec.version = Doorkeeper::JWT.gem_version - spec.authors = ["Chris Warren"] - spec.email = ["chris@expectless.com"] + spec.name = 'doorkeeper-jwt' + spec.version = Doorkeeper::JWT::VERSION + spec.authors = ['Chris Warren'] + spec.email = ['chris@expectless.com'] - spec.summary = 'JWT token generator for Doorkeeper' - spec.description = 'JWT token generator extension for Doorkeeper' - spec.homepage = "https://github.com/chriswarren/doorkeeper-jwt" - spec.license = "MIT" + spec.summary = 'JWT token generator for Doorkeeper' + spec.description = 'JWT token generator extension for Doorkeeper' + spec.homepage = 'https://github.com/chriswarren/doorkeeper-jwt' + spec.license = 'MIT' - spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - spec.bindir = "exe" + spec.bindir = 'exe' + spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = ['lib'] - spec.add_dependency "jwt", "~> 2.1.0", ">= 2.1.0" + spec.add_dependency 'jwt', '~> 2.1' - spec.add_development_dependency "bundler", "~> 1.8", ">= 1.8" - spec.add_development_dependency "pry", "~> 0" - spec.add_development_dependency "rake", "~> 10.0", ">= 10.0" - spec.add_development_dependency "rspec", "~> 3.2.0", ">= 3.2" + spec.add_development_dependency 'bundler', '~> 1.16' + spec.add_development_dependency 'pry', '~> 0' + spec.add_development_dependency 'rake', '~> 12.3' + spec.add_development_dependency 'rspec', '~> 3.8' end diff --git a/lib/doorkeeper-jwt/doorkeeper-jwt.rb b/lib/doorkeeper-jwt/doorkeeper-jwt.rb deleted file mode 100644 index b9733f8..0000000 --- a/lib/doorkeeper-jwt/doorkeeper-jwt.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -require 'doorkeeper-jwt/version' diff --git a/lib/doorkeeper-jwt/version.rb b/lib/doorkeeper-jwt/version.rb deleted file mode 100644 index 9612751..0000000 --- a/lib/doorkeeper-jwt/version.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -module Doorkeeper - module JWT - def self.gem_version - Gem::Version.new VERSION::STRING - end - - module VERSION - # Semantic versioning - MAJOR = 0 - MINOR = 3 - TINY = 0 - PRE = nil - - # Full version number - STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') - end - end -end diff --git a/lib/doorkeeper-jwt.rb b/lib/doorkeeper/jwt.rb similarity index 77% rename from lib/doorkeeper-jwt.rb rename to lib/doorkeeper/jwt.rb index acdab70..d4c6cfb 100644 --- a/lib/doorkeeper-jwt.rb +++ b/lib/doorkeeper/jwt.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require "doorkeeper-jwt/version" -require "doorkeeper-jwt/config" +require 'doorkeeper/jwt/version' +require 'doorkeeper/jwt/config' require 'jwt' module Doorkeeper @@ -19,11 +19,11 @@ def generate(opts = {}) private def token_payload(opts = {}) - Doorkeeper::JWT.configuration.token_payload.call opts + Doorkeeper::JWT.configuration.token_payload.call(opts) end def token_headers(opts = {}) - Doorkeeper::JWT.configuration.token_headers.call opts + Doorkeeper::JWT.configuration.token_headers.call(opts) end def secret_key(opts) @@ -33,6 +33,7 @@ def secret_key(opts) return secret_key_file unless secret_key_file.nil? return rsa_key if rsa_encryption? return ecdsa_key if ecdsa_encryption? + Doorkeeper::JWT.configuration.secret_key end @@ -43,7 +44,8 @@ def secret_key_file end def encryption_method - return "none" unless Doorkeeper::JWT.configuration.encryption_method + return 'none' unless Doorkeeper::JWT.configuration.encryption_method + Doorkeeper::JWT.configuration.encryption_method.to_s.upcase end @@ -53,14 +55,17 @@ def use_application_secret? def application_secret(opts) if opts[:application].nil? - raise "JWT `use_application_secret` is enabled but application is " \ - "nil. This can happen if `client_id` was absent in the request " \ - "params." + raise( + 'JWT `use_application_secret` is enabled, but application is nil.' \ + ' This can happen if `client_id` was absent in the request params.' + ) end if opts[:application][:secret].nil? - raise "JWT `use_application_secret` is enabled but the application " \ - "secret is nil." + raise( + 'JWT `use_application_secret` is enabled, but the application' \ + ' secret is nil.' + ) end opts[:application][:secret] diff --git a/lib/doorkeeper-jwt/config.rb b/lib/doorkeeper/jwt/config.rb similarity index 52% rename from lib/doorkeeper-jwt/config.rb rename to lib/doorkeeper/jwt/config.rb index fed2aab..e412a35 100644 --- a/lib/doorkeeper-jwt/config.rb +++ b/lib/doorkeeper/jwt/config.rb @@ -13,7 +13,7 @@ def self.configure(&block) end def self.configuration - @config || (raise MissingConfiguration) + @config || raise(MissingConfiguration) end class Config @@ -27,76 +27,73 @@ def build @config end - def use_application_secret(use_application_secret) - @config.instance_variable_set( - '@use_application_secret', - use_application_secret - ) + def use_application_secret(value) + @config.instance_variable_set('@use_application_secret', value) end - def secret_key(secret_key) - @config.instance_variable_set('@secret_key', secret_key) + def secret_key(value) + @config.instance_variable_set('@secret_key', value) end - def secret_key_path(secret_key_path) - @config.instance_variable_set('@secret_key_path', secret_key_path) + def secret_key_path(value) + @config.instance_variable_set('@secret_key_path', value) end - def encryption_method(encryption_method) - @config.instance_variable_set( - '@encryption_method', encryption_method - ) + def encryption_method(value) + @config.instance_variable_set('@encryption_method', value) end end module Option - # Defines configuration option + # Defines configuration options. # - # When you call option, it defines two methods. One method will take place - # in the +Config+ class and the other method will take place in the - # +Builder+ class. + # When you call option, it defines two methods. One method will take + # place in the +Config+ class and the other method will take place in + # the +Builder+ class. # - # The +name+ parameter will set both builder method and config attribute. - # If the +:as+ option is defined, the builder method will be the specified - # option while the config attribute will be the +name+ parameter. + # The +name+ parameter will set both builder method and config + # attribute. If the +:as+ option is defined, the builder method will be + # the specified option while the config attribute will be the +name+ + # parameter. # - # If you want to introduce another level of config DSL you can - # define +builder_class+ parameter. - # Builder should take a block as the initializer parameter and respond to function +build+ - # that returns the value of the config attribute. + # If you want to introduce another level of config DSL you can define + # +builder_class+ parameter. Builder should take a block as the + # initializer parameter and respond to function +build+ that returns the + # value of the config attribute. # # ==== Options # - # * [:+as+] Set the builder method that goes inside +configure+ block - # * [+:default+] The default value in case no option was set + # * [+:as+] Set the builder method that goes inside +configure+ block. + # * [+:default+] The default value in case no option was set. # # ==== Examples # - # option :name - # option :name, as: :set_name - # option :name, default: 'My Name' - # option :scopes builder_class: ScopesBuilder - # + # option :name + # option :name, as: :set_name + # option :name, default: 'My Name' + # option :scopes, builder_class: ScopesBuilder def option(name, options = {}) attribute = options[:as] || name attribute_builder = options[:builder_class] + attribute_symbol = :"@#{attribute}" Builder.instance_eval do define_method name do |*args, &block| # TODO: is builder_class option being used? - value = if attribute_builder - attribute_builder.new(&block).build - else - block || args.first - end - - @config.instance_variable_set(:"@#{attribute}", value) + value = + if attribute_builder + attribute_builder.new(&block).build + else + block || args.first + end + + @config.instance_variable_set(attribute_symbol, value) end end define_method attribute do |*| - if instance_variable_defined?(:"@#{attribute}") - instance_variable_get(:"@#{attribute}") + if instance_variable_defined?(attribute_symbol) + instance_variable_get(attribute_symbol) else options[:default] end @@ -112,8 +109,11 @@ def extended(base) extend Option - option :token_payload, - default: proc { { token: SecureRandom.method(:hex) } } + option( + :token_payload, + default: proc { { token: SecureRandom.method(:hex) } } + ) + option :token_headers, default: proc { {} } option :use_application_secret, default: false option :secret_key, default: nil diff --git a/lib/doorkeeper/jwt/version.rb b/lib/doorkeeper/jwt/version.rb new file mode 100644 index 0000000..ce3744c --- /dev/null +++ b/lib/doorkeeper/jwt/version.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Doorkeeper + module JWT + VERSION = '0.4.0' + end +end diff --git a/spec/doorkeeper-jwt/doorkeeper-jwt_spec.rb b/spec/doorkeeper-jwt/doorkeeper-jwt_spec.rb deleted file mode 100644 index d66f6c2..0000000 --- a/spec/doorkeeper-jwt/doorkeeper-jwt_spec.rb +++ /dev/null @@ -1,219 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Doorkeeper::JWT do - it 'has a version number' do - expect(Doorkeeper::JWT::VERSION).not_to be nil - end - - describe ".generate" do - it "creates a JWT token" do - Doorkeeper::JWT.configure do - end - - token = Doorkeeper::JWT.generate({}) - decoded_token = ::JWT.decode(token, nil, false) - expect(decoded_token[0]).to be_a(Hash) - expect(decoded_token[0]["token"]).to be_a(String) - expect(decoded_token[1]["alg"]).to eq "none" - end - - it "creates a JWT token with a custom payload" do - Doorkeeper::JWT.configure do - token_payload do - { - foo: "bar" - } - end - end - - token = Doorkeeper::JWT.generate({}) - decoded_token = ::JWT.decode(token, nil, false) - expect(decoded_token[0]).to be_a(Hash) - expect(decoded_token[0]["foo"]).to eq "bar" - expect(decoded_token[1]["alg"]).to eq "none" - end - - it "creates a JWT token with custom dynamic headers" do - Doorkeeper::JWT.configure do - token_headers do |opts| - { - kid: opts[:application][:uid] - } - end - end - - token = Doorkeeper::JWT.generate(application: { uid: "foo" }) - decoded_token = ::JWT.decode(token, nil, false) - expect(decoded_token[1]).to be_a(Hash) - expect(decoded_token[1]["alg"]).to eq "none" - expect(decoded_token[1]["kid"]).to eq "foo" - end - - it "creates a signed JWT token" do - Doorkeeper::JWT.configure do - secret_key "super secret" - end - - token = Doorkeeper::JWT.generate({}) - decoded_token = ::JWT.decode(token, "super secret", false) - expect(decoded_token[0]).to be_a(Hash) - expect(decoded_token[0]["token"]).to be_a(String) - expect(decoded_token[1]["alg"]).to eq "none" - end - - it "creates a signed encrypted JWT token" do - Doorkeeper::JWT.configure do - secret_key "super secret" - encryption_method :hs256 - end - - token = Doorkeeper::JWT.generate({}) - algorithm = { algorithm: "HS256" } - decoded_token = ::JWT.decode(token, "super secret", true, algorithm) - expect(decoded_token[0]).to be_a(Hash) - expect(decoded_token[0]["token"]).to be_a(String) - expect(decoded_token[1]["alg"]).to eq "HS256" - end - - it "creates a signed encrypted JWT token with a custom payload" do - Doorkeeper::JWT.configure do - token_payload do - { - foo: "bar" - } - end - secret_key "super secret" - encryption_method :hs256 - end - - token = Doorkeeper::JWT.generate({}) - algorithm = { algorithm: "HS256" } - decoded_token = ::JWT.decode(token, "super secret", true, algorithm) - expect(decoded_token[0]).to be_a(Hash) - expect(decoded_token[0]["foo"]).to eq "bar" - expect(decoded_token[1]["alg"]).to eq "HS256" - end - - it "creates a signed encrypted JWT token with a custom dynamic payload" do - Doorkeeper::JWT.configure do - token_payload do |opts| - { - foo: "bar_#{opts[:resource_owner_id]}" - } - end - secret_key "super secret" - encryption_method :hs256 - end - - token = Doorkeeper::JWT.generate(resource_owner_id: 1) - algorithm = { algorithm: "HS256" } - decoded_token = ::JWT.decode(token, "super secret", true, algorithm) - expect(decoded_token[0]).to be_a(Hash) - expect(decoded_token[0]["foo"]).to eq "bar_1" - expect(decoded_token[1]["alg"]).to eq "HS256" - end - - it "creates a signed JWT token encrypted with an RSA key from a file" do - Doorkeeper::JWT.configure do - token_payload do - { - foo: "bar" - } - end - secret_key_path "spec/support/1024key.pem" - encryption_method :rs512 - end - - token = Doorkeeper::JWT.generate({}) - secret_key = OpenSSL::PKey::RSA.new File.read("spec/support/1024key.pem") - decoded_token = ::JWT.decode(token, secret_key, true, algorithm: "RS512") - expect(decoded_token[0]).to be_a(Hash) - expect(decoded_token[0]["foo"]).to eq "bar" - expect(decoded_token[1]["alg"]).to eq "RS512" - end - - it "creates a signed JWT token encrypted with an RSA key from a string" do - secret_key = OpenSSL::PKey::RSA.new(1024) - Doorkeeper::JWT.configure do - token_payload do - { - foo: "bar" - } - end - secret_key secret_key.to_s - encryption_method :rs512 - end - - token = Doorkeeper::JWT.generate({}) - decoded_token = ::JWT.decode(token, secret_key, true, algorithm: "RS512") - expect(decoded_token[0]).to be_a(Hash) - expect(decoded_token[0]["foo"]).to eq "bar" - expect(decoded_token[1]["alg"]).to eq "RS512" - end - - it "creates a signed JWT token encrypted with an ECDSA key from a file" do - Doorkeeper::JWT.configure do - token_payload do - { - foo: "bar" - } - end - secret_key_path "spec/support/512key.pem" - encryption_method :es512 - end - - token = Doorkeeper::JWT.generate({}) - key_file = File.read("spec/support/512key_pub.pem") - secret_key = OpenSSL::PKey::EC.new key_file - decoded_token = ::JWT.decode(token, secret_key, true, algorithm: "ES512") - expect(decoded_token[0]).to be_a(Hash) - expect(decoded_token[0]["foo"]).to eq "bar" - expect(decoded_token[1]["alg"]).to eq "ES512" - end - - it "creates a signed JWT token encrypted with an ECDSA key from a string" do - secret_key = OpenSSL::PKey::EC.new("secp521r1") - secret_key.generate_key - public_key = OpenSSL::PKey::EC.new secret_key - public_key.private_key = nil - - Doorkeeper::JWT.configure do - token_payload do - { - foo: "bar" - } - end - secret_key secret_key - encryption_method :es512 - end - - token = Doorkeeper::JWT.generate({}) - decoded_token = ::JWT.decode(token, public_key, true, algorithm: "ES512") - expect(decoded_token[0]).to be_a(Hash) - expect(decoded_token[0]["foo"]).to eq "bar" - expect(decoded_token[1]["alg"]).to eq "ES512" - end - - it "creates a signed JWT token encrypted with an app secret" do - secret_key = OpenSSL::PKey::RSA.new(1024) - Doorkeeper::JWT.configure do - use_application_secret true - token_payload do - { - foo: "bar" - } - end - secret_key secret_key.to_s - encryption_method :rs512 - end - - token = Doorkeeper::JWT.generate(application: { secret: secret_key }) - decoded_token = ::JWT.decode(token, secret_key, true, algorithm: "RS512") - expect(decoded_token[0]).to be_a(Hash) - expect(decoded_token[0]["foo"]).to eq "bar" - expect(decoded_token[1]["alg"]).to eq "RS512" - end - end -end diff --git a/spec/doorkeeper-jwt/config_spec.rb b/spec/doorkeeper/jwt/configuration_spec.rb similarity index 65% rename from spec/doorkeeper-jwt/config_spec.rb rename to spec/doorkeeper/jwt/configuration_spec.rb index 799cc7b..343fe99 100644 --- a/spec/doorkeeper-jwt/config_spec.rb +++ b/spec/doorkeeper/jwt/configuration_spec.rb @@ -2,23 +2,24 @@ require 'spec_helper' -describe Doorkeeper::JWT, 'configuration' do - subject { Doorkeeper::JWT.configuration } +describe(Doorkeeper::JWT, '#configuration') do + subject(:configuration) { Doorkeeper::JWT.configuration } describe 'token_payload' do it 'is nil by default' do Doorkeeper::JWT.configure {} - expect(subject.token_payload).to be_a(Proc) + expect(configuration.token_payload).to be_a(Proc) end it 'sets the block that is accessible via authenticate_admin' do block = proc {} + Doorkeeper::JWT.configure do token_payload(&block) end - expect(subject.token_payload).to eq(block) + expect(configuration.token_payload).to eq(block) end end @@ -26,16 +27,17 @@ it 'is nil by default' do Doorkeeper::JWT.configure {} - expect(subject.token_headers).to be_a(Proc) + expect(configuration.token_headers).to be_a(Proc) end it 'sets the block that is accessible via authenticate_admin' do block = proc {} + Doorkeeper::JWT.configure do token_headers(&block) end - expect(subject.token_headers).to eq(block) + expect(configuration.token_headers).to eq(block) end end @@ -43,7 +45,7 @@ it 'defaults to nil' do Doorkeeper::JWT.configure {} - expect(subject.encryption_method).to be_nil + expect(configuration.encryption_method).to be_nil end it 'can change the value' do @@ -51,7 +53,7 @@ encryption_method :rs512 end - expect(subject.encryption_method).to eq :rs512 + expect(configuration.encryption_method).to eq :rs512 end end @@ -59,7 +61,7 @@ it 'defaults to false' do Doorkeeper::JWT.configure {} - expect(subject.use_application_secret).to be false + expect(configuration.use_application_secret).to be false end it "changes the value of secret_key to the application's secret" do @@ -67,7 +69,7 @@ use_application_secret true end - expect(subject.use_application_secret).to be true + expect(configuration.use_application_secret).to be true end end @@ -75,7 +77,7 @@ it 'defaults to nil' do Doorkeeper::JWT.configure {} - expect(subject.secret_key).to be_nil + expect(configuration.secret_key).to be_nil end it 'can change the value' do @@ -83,7 +85,7 @@ secret_key 'foo' end - expect(subject.secret_key).to eq 'foo' + expect(configuration.secret_key).to eq 'foo' end end @@ -91,7 +93,7 @@ it 'defaults to nil' do Doorkeeper::JWT.configure {} - expect(subject.secret_key_path).to be_nil + expect(configuration.secret_key_path).to be_nil end it 'can change the value' do @@ -99,13 +101,12 @@ secret_key_path File.join('..', 'support', '1024key.pem') end - expect(subject.secret_key_path).to eq '../support/1024key.pem' + expect(configuration.secret_key_path).to eq '../support/1024key.pem' end end it 'raises an exception when configuration is not set' do - expect do - Doorkeeper::JWT.configuration - end.to raise_error Doorkeeper::JWT::MissingConfiguration + expect { Doorkeeper::JWT.configuration } + .to raise_error Doorkeeper::JWT::MissingConfiguration end end diff --git a/spec/doorkeeper/jwt_spec.rb b/spec/doorkeeper/jwt_spec.rb new file mode 100644 index 0000000..47996e1 --- /dev/null +++ b/spec/doorkeeper/jwt_spec.rb @@ -0,0 +1,233 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Doorkeeper::JWT do + it 'has a version number' do + expect(Doorkeeper::JWT::VERSION).not_to be nil + end + + describe '.generate' do + it 'creates a JWT token' do + described_class.configure {} + + token = described_class.generate({}) + decoded_token = ::JWT.decode(token, nil, false) + + expect(decoded_token[0]).to be_a(Hash) + expect(decoded_token[0]['token']).to be_a(String) + expect(decoded_token[1]).to be_a(Hash) + expect(decoded_token[1]['alg']).to eq 'none' + end + + it 'creates a JWT token with a custom payload' do + described_class.configure do + token_payload do + { foo: 'bar' } + end + end + + token = described_class.generate({}) + decoded_token = ::JWT.decode(token, nil, false) + + expect(decoded_token[0]).to be_a(Hash) + expect(decoded_token[0]['foo']).to eq 'bar' + expect(decoded_token[1]).to be_a(Hash) + expect(decoded_token[1]['alg']).to eq 'none' + end + + it 'creates a JWT token with custom dynamic headers' do + described_class.configure do + token_headers do |opts| + { kid: opts[:application][:uid] } + end + end + + token = described_class.generate(application: { uid: 'foo' }) + decoded_token = ::JWT.decode(token, nil, false) + + expect(decoded_token[1]).to be_a(Hash) + expect(decoded_token[1]['alg']).to eq 'none' + expect(decoded_token[1]['kid']).to eq 'foo' + end + + it 'creates a signed JWT token' do + described_class.configure do + secret_key 'super secret' + end + + token = described_class.generate({}) + decoded_token = ::JWT.decode(token, 'super secret', false) + + expect(decoded_token[0]).to be_a(Hash) + expect(decoded_token[0]['token']).to be_a(String) + expect(decoded_token[1]).to be_a(Hash) + expect(decoded_token[1]['alg']).to eq 'none' + end + + it 'creates a signed encrypted JWT token' do + described_class.configure do + secret_key 'super secret' + encryption_method :hs256 + end + + token = described_class.generate({}) + algorithm = { algorithm: 'HS256' } + decoded_token = ::JWT.decode(token, 'super secret', true, algorithm) + + expect(decoded_token[0]).to be_a(Hash) + expect(decoded_token[0]['token']).to be_a(String) + expect(decoded_token[1]).to be_a(Hash) + expect(decoded_token[1]['alg']).to eq 'HS256' + end + + it 'creates a signed encrypted JWT token with a custom payload' do + described_class.configure do + token_payload do + { foo: 'bar' } + end + + secret_key 'super secret' + encryption_method :hs256 + end + + token = described_class.generate({}) + algorithm = { algorithm: 'HS256' } + decoded_token = ::JWT.decode(token, 'super secret', true, algorithm) + + expect(decoded_token[0]).to be_a(Hash) + expect(decoded_token[0]['foo']).to eq 'bar' + expect(decoded_token[1]).to be_a(Hash) + expect(decoded_token[1]['alg']).to eq 'HS256' + end + + it 'creates a signed encrypted JWT token with a custom dynamic payload' do + described_class.configure do + token_payload do |opts| + { foo: "bar_#{opts[:resource_owner_id]}" } + end + + secret_key 'super secret' + encryption_method :hs256 + end + + token = described_class.generate(resource_owner_id: 1) + algorithm = { algorithm: 'HS256' } + decoded_token = ::JWT.decode(token, 'super secret', true, algorithm) + + expect(decoded_token[0]).to be_a(Hash) + expect(decoded_token[0]['foo']).to eq 'bar_1' + expect(decoded_token[1]).to be_a(Hash) + expect(decoded_token[1]['alg']).to eq 'HS256' + end + + it 'creates a signed JWT token encrypted with an RSA key from a file' do + described_class.configure do + token_payload do + { foo: 'bar' } + end + + secret_key_path 'spec/support/1024key.pem' + encryption_method :rs512 + end + + token = described_class.generate({}) + secret_key = OpenSSL::PKey::RSA.new File.read('spec/support/1024key.pem') + decoded_token = ::JWT.decode(token, secret_key, true, algorithm: 'RS512') + + expect(decoded_token[0]).to be_a(Hash) + expect(decoded_token[0]['foo']).to eq 'bar' + expect(decoded_token[1]).to be_a(Hash) + expect(decoded_token[1]['alg']).to eq 'RS512' + end + + it 'creates a signed JWT token encrypted with an RSA key from a string' do + secret_key = OpenSSL::PKey::RSA.new(1024) + + described_class.configure do + token_payload do + { foo: 'bar' } + end + + secret_key secret_key.to_s + encryption_method :rs512 + end + + token = described_class.generate({}) + decoded_token = ::JWT.decode(token, secret_key, true, algorithm: 'RS512') + + expect(decoded_token[0]).to be_a(Hash) + expect(decoded_token[0]['foo']).to eq 'bar' + expect(decoded_token[1]).to be_a(Hash) + expect(decoded_token[1]['alg']).to eq 'RS512' + end + + it 'creates a signed JWT token encrypted with an ECDSA key from a file' do + described_class.configure do + token_payload do + { foo: 'bar' } + end + + secret_key_path 'spec/support/512key.pem' + encryption_method :es512 + end + + token = described_class.generate({}) + key_file = File.read('spec/support/512key_pub.pem') + secret_key = OpenSSL::PKey::EC.new key_file + decoded_token = ::JWT.decode(token, secret_key, true, algorithm: 'ES512') + + expect(decoded_token[0]).to be_a(Hash) + expect(decoded_token[0]['foo']).to eq 'bar' + expect(decoded_token[1]).to be_a(Hash) + expect(decoded_token[1]['alg']).to eq 'ES512' + end + + it 'creates a signed JWT token encrypted with an ECDSA key from a string' do + secret_key = OpenSSL::PKey::EC.new('secp521r1') + secret_key.generate_key + public_key = OpenSSL::PKey::EC.new secret_key + public_key.private_key = nil + + described_class.configure do + token_payload do + { foo: 'bar' } + end + + secret_key secret_key + encryption_method :es512 + end + + token = described_class.generate({}) + decoded_token = ::JWT.decode(token, public_key, true, algorithm: 'ES512') + + expect(decoded_token[0]).to be_a(Hash) + expect(decoded_token[0]['foo']).to eq 'bar' + expect(decoded_token[1]).to be_a(Hash) + expect(decoded_token[1]['alg']).to eq 'ES512' + end + + it 'creates a signed JWT token encrypted with an app secret' do + secret_key = OpenSSL::PKey::RSA.new(1024) + + described_class.configure do + use_application_secret true + + token_payload do + { foo: 'bar' } + end + + secret_key secret_key.to_s + encryption_method :rs512 + end + + token = described_class.generate(application: { secret: secret_key }) + decoded_token = ::JWT.decode(token, secret_key, true, algorithm: 'RS512') + + expect(decoded_token[0]).to be_a(Hash) + expect(decoded_token[0]['foo']).to eq 'bar' + expect(decoded_token[1]).to be_a(Hash) + expect(decoded_token[1]['alg']).to eq 'RS512' + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a4bd419..5a19a08 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,5 +4,6 @@ Coveralls.wear! $LOAD_PATH.unshift File.expand_path('../lib', __dir__) -require 'doorkeeper-jwt' + +require 'doorkeeper/jwt' require 'pry'