fb-jwt-auth 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff3e7406a923af4e7374f7855ddce3de5bbe9e46267b63a2d06c92a74f3ec13a
4
- data.tar.gz: 1a0db0196134bed15fec85fb83006f0254f5dc0b2e96f02644b4d452499a121c
3
+ metadata.gz: 45ebbebff675bd2afe201e48c9d3d711ae255619511e00318f6cd39aef8094c0
4
+ data.tar.gz: e1d51f15d8b8a4a55be59228edce5aa3723b74bd312d0b87f868e81ecd3bcdb3
5
5
  SHA512:
6
- metadata.gz: 9ef0173a6261f0b8f8377f1987ab3d8c16b7364f25209ab10094240a096e2b9ef24f35944f12ad81a06a85357da1e48c76899285dcd40f8210254993da44a81b
7
- data.tar.gz: aee6abbd74914c5ba92158c5e6eb3ba3b62bd55e36bd5fa57d2877fb76a5ebd806317072a5ee8d6fd91b7318af788ec89da4bf15ad421d66426a908636b8d1c0
6
+ metadata.gz: 56e7552bcdede5d4ce45200ab5160a790128e80ee0a6f276d0e4d7f9f4515de14db7a9efab79554df31593ee801e9a983d1af413327427b1c88e184a80e88042
7
+ data.tar.gz: 395d1d6f094db54c9411bc20d6804c94ea56f7a52e60a64e4496f6ed75d2bb5bd52817ada3fbc7058a404fa47db4b48b850df16ccb9a4ae96236b4591a13d2c6
@@ -0,0 +1,63 @@
1
+ version: 2.1
2
+ orbs:
3
+ slack: circleci/slack@3.4.2
4
+
5
+ jobs:
6
+ test:
7
+ docker:
8
+ - image: cimg/ruby:2.7.2
9
+ steps:
10
+ - checkout
11
+ - run:
12
+ name: Install
13
+ command: bundle install
14
+ - run:
15
+ name: Test
16
+ command: bundle exec rspec
17
+ - slack/status: &slack_status
18
+ fail_only: true
19
+ only_for_branches: main
20
+ failure_message: ":facepalm: Failed job $CIRCLE_JOB :homer-disappear:"
21
+ include_job_number_field: false
22
+ publish:
23
+ docker:
24
+ - image: cimg/ruby:2.7.2
25
+ steps:
26
+ - checkout
27
+ - run:
28
+ name: Install
29
+ command: bundle install
30
+ - run:
31
+ name: Setup Rubygems
32
+ command: |
33
+ mkdir ~/.gem
34
+ echo -e "---\r\n:rubygems_api_key: $RUBYGEMS_API_KEY" > ~/.gem/credentials
35
+ chmod 0600 /home/circleci/.gem/credentials
36
+ - run:
37
+ name: Publish to Rubygems
38
+ command: |
39
+ set -e
40
+
41
+ VERSION=$(ruby -e "require './lib/fb/jwt/auth/version.rb'; puts Fb::Jwt::Auth::VERSION")
42
+ PUBLISHED_VERSION=$(curl https://rubygems.org/api/v1/versions/fb-jwt-auth/latest.json | sed -e 's/[{}]/''/g' | sed s/\"//g | awk -v RS=',' -F: '$1=="version"{print $2}')
43
+
44
+ if [ "$VERSION" != "$PUBLISHED_VERSION" ]
45
+ then
46
+ bundle exec gem build fb-jwt-auth.gemspec
47
+ bundle exec gem push fb-jwt-auth-*.gem
48
+ curl -X POST -H 'Content-type: application/json' --data "{\"text\":\":woohoo: Successfully published ${CIRCLE_PROJECT_REPONAME} ${VERSION} :ship_it_parrot:\"}" "$SLACK_WEBHOOK"
49
+ fi
50
+ - slack/status: *slack_status
51
+
52
+ workflows:
53
+ commit-workflow:
54
+ jobs:
55
+ - test
56
+ - publish:
57
+ requires:
58
+ - test
59
+ filters:
60
+ tags:
61
+ only: /.*/
62
+ branches:
63
+ only: main
data/.gitignore CHANGED
@@ -11,3 +11,4 @@
11
11
  .rspec_status
12
12
  Gemfile.lock
13
13
  .byebug_history
14
+ *.gem
@@ -0,0 +1,18 @@
1
+ # 0.3.0
2
+ * Request non cached version of public key if first validition fails
3
+
4
+ # v0.2.2
5
+ * Add token not present exception when token is empty
6
+
7
+ # v0.2.1
8
+ * Add better error messages
9
+
10
+ # v0.2.0
11
+
12
+ * Add service token cache v3 implementation
13
+ * All necessary information (iat, sub and application, namespace) should be on
14
+ the access token
15
+
16
+ # v0.1.0
17
+
18
+ * Add service token cache v2 implementation
data/README.md CHANGED
@@ -18,11 +18,39 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- ```
21
+ ```ruby
22
22
  Fb::Jwt::Auth.configure do |config|
23
+ # Service token cache domain
24
+ #
23
25
  config.service_token_cache_root_url = ENV['SERVICE_TOKEN_CACHE_ROOT_URL']
24
26
  end
27
+ ```
28
+ In order to generate the service access token we need to use `Fb::Jwt::Auth::ServiceAccessToken.new.generate` or if you require a subject, `Fb::Jwt::Auth::ServiceAccessToken.new(subject: subject).generate`
29
+
30
+ In the case you need to configure the service access token as a client
31
+ ```ruby
32
+ Fb::Jwt::Auth.configure do |config|
33
+ config.issuer = 'fb-editor'
34
+ config.namespace = 'formbuilder-saas-test'
35
+ config.encoded_private_key = 'base64 encoded private key'
36
+ end
37
+ ```
38
+
39
+ ### Using other endpoint versions
40
+
41
+ Service token cache can have different versions of authenticating a service.
42
+
43
+ You can configure the version:
44
+
45
+ ```ruby
46
+ Fb::Jwt::Auth.configure do |config|
47
+ config.service_token_cache_api_version = :v3
48
+ end
49
+ ```
25
50
 
51
+ ### Verifying the token
52
+
53
+ ```ruby
26
54
  Fb::Jwt::Auth.new(
27
55
  access_token: request.headers['x-access-token-v2'],
28
56
  key: 'fb-editor', # service name
@@ -1,24 +1,23 @@
1
1
  require 'fb/jwt/auth/version'
2
2
  require 'openssl'
3
3
  require 'jwt'
4
- require 'active_support/core_ext'
4
+ require 'active_support/all'
5
5
 
6
6
  module Fb
7
7
  module Jwt
8
8
  class Auth
9
- def self.service_token_cache_root_url=(value)
10
- @@service_token_cache_root_url = value
11
- end
12
-
13
- def self.service_token_cache_root_url
14
- @@service_token_cache_root_url
15
- end
9
+ cattr_accessor :service_token_cache_root_url,
10
+ :service_token_cache_api_version,
11
+ :encoded_private_key,
12
+ :issuer,
13
+ :namespace
16
14
 
17
15
  def self.configure(&block)
18
16
  yield self
19
17
  end
20
18
 
21
19
  autoload :ServiceTokenClient, 'fb/jwt/auth/service_token_client'
20
+ autoload :ServiceAccessToken, 'fb/jwt/auth/service_access_token'
22
21
 
23
22
  class TokenNotPresentError < StandardError
24
23
  end
@@ -29,9 +28,15 @@ module Fb
29
28
  class TokenExpiredError < StandardError
30
29
  end
31
30
 
31
+ class IssuerNotPresentError < StandardError
32
+ end
33
+
34
+ class NamespaceNotPresentError < StandardError
35
+ end
36
+
32
37
  attr_accessor :token, :key, :leeway, :logger
33
38
 
34
- def initialize(token:, key:, leeway:, logger:)
39
+ def initialize(token:, key: nil, leeway:, logger:)
35
40
  @token = token
36
41
  @key = key
37
42
  @leeway = leeway
@@ -39,19 +44,14 @@ module Fb
39
44
  end
40
45
 
41
46
  def verify!
42
- raise TokenNotPresentError if token.nil?
47
+ raise TokenNotPresentError.new('Token is not present') if token.blank?
48
+
49
+ application_details = find_application_info
43
50
 
44
51
  begin
45
- hmac_secret = public_key(key)
46
- payload, _header = JWT.decode(
47
- token,
48
- hmac_secret,
49
- true,
50
- exp_leeway: leeway,
51
- algorithm: 'RS256'
52
- )
52
+ payload, _header = retrieve_and_decode_public_key(application_details)
53
53
  rescue StandardError => e
54
- error_message = "Couldn't parse that token - error #{e}"
54
+ error_message = "Token is not valid: error #{e}"
55
55
  logger.debug(error_message)
56
56
  raise TokenNotValidError.new(error_message)
57
57
  end
@@ -61,7 +61,7 @@ module Fb
61
61
  iat_skew = payload['iat'].to_i - Time.zone.now.to_i
62
62
 
63
63
  if iat_skew.abs > leeway.to_i
64
- error_message = "iat skew is #{iat_skew}, max is #{leeway} - INVALID"
64
+ error_message = "Token has expired: iat skew is #{iat_skew}, max is #{leeway}"
65
65
  logger.debug(error_message)
66
66
 
67
67
  raise TokenExpiredError.new(error_message)
@@ -71,8 +71,42 @@ module Fb
71
71
  payload
72
72
  end
73
73
 
74
- def public_key
75
- OpenSSL::PKey::RSA.new(ServiceTokenClient.new(key).public_key)
74
+ def retrieve_and_decode_public_key(application_details)
75
+ hmac_secret = public_key(application_details)
76
+ decode(hmac_secret: hmac_secret)
77
+ rescue JWT::VerificationError
78
+ logger.debug('First validation failed. Requesting non cached public key')
79
+ hmac_secret = public_key(application_details.merge(ignore_cache: true))
80
+ decode(hmac_secret: hmac_secret)
81
+ end
82
+
83
+ def decode(verify: true, hmac_secret: nil)
84
+ JWT.decode(
85
+ token,
86
+ hmac_secret,
87
+ verify,
88
+ exp_leeway: leeway,
89
+ algorithm: 'RS256'
90
+ )
91
+ end
92
+
93
+ def find_application_info
94
+ return { application: key } if key
95
+
96
+ payload, _header = decode(verify: false)
97
+ application = payload['iss']
98
+ namespace = payload['namespace']
99
+
100
+ raise IssuerNotPresentError.new('Issuer is not present in the token') unless application
101
+ raise NamespaceNotPresentError.new('Namespace is not present in the token') unless namespace
102
+
103
+ { application: application, namespace: namespace}
104
+ end
105
+
106
+ def public_key(attributes)
107
+ OpenSSL::PKey::RSA.new(
108
+ ServiceTokenClient.new(attributes).public_key
109
+ )
76
110
  end
77
111
  end
78
112
  end
@@ -0,0 +1,45 @@
1
+ module Fb
2
+ module Jwt
3
+ class Auth
4
+ class ServiceAccessToken
5
+ attr_reader :encoded_private_key,
6
+ :issuer,
7
+ :subject,
8
+ :namespace
9
+
10
+ def initialize(subject: nil)
11
+ @subject = subject
12
+ @encoded_private_key = Fb::Jwt::Auth.encoded_private_key
13
+ @namespace = Fb::Jwt::Auth.namespace
14
+ @issuer = Fb::Jwt::Auth.issuer
15
+ end
16
+
17
+ def generate
18
+ return '' if encoded_private_key.blank?
19
+
20
+ private_key = OpenSSL::PKey::RSA.new(
21
+ Base64.strict_decode64(encoded_private_key.chomp)
22
+ )
23
+
24
+ JWT.encode(
25
+ token,
26
+ private_key,
27
+ 'RS256'
28
+ )
29
+ end
30
+
31
+ private
32
+
33
+ def token
34
+ payload = {
35
+ iss: issuer,
36
+ iat: Time.current.to_i
37
+ }
38
+ payload[:sub] = subject if subject.present?
39
+ payload[:namespace] = namespace if namespace.present?
40
+ payload
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -5,11 +5,19 @@ require 'base64'
5
5
  class Fb::Jwt::Auth::ServiceTokenClient
6
6
  class ServiceTokenCacheError < StandardError; end
7
7
 
8
- attr_accessor :key, :root_url
8
+ ENDPOINTS = {
9
+ v2: '/service/v2/%{application}',
10
+ v3: '/v3/applications/%{application}/namespaces/%{namespace}'
11
+ }
9
12
 
10
- def initialize(key)
11
- @key = key
13
+ attr_accessor :application, :namespace, :root_url, :api_version
14
+
15
+ def initialize(application:, namespace: nil, ignore_cache: false)
16
+ @application = application
17
+ @namespace = namespace
18
+ @ignore_cache = ignore_cache
12
19
  @root_url = Fb::Jwt::Auth.service_token_cache_root_url
20
+ @api_version = Fb::Jwt::Auth.service_token_cache_api_version || :v2
13
21
  end
14
22
 
15
23
  def public_key
@@ -31,7 +39,21 @@ class Fb::Jwt::Auth::ServiceTokenClient
31
39
 
32
40
  private
33
41
 
42
+ attr_reader :ignore_cache
43
+
34
44
  def public_key_uri
35
- URI.join(@root_url, '/service/v2/', key)
45
+ URI.join(root_url, "#{version_url}#{query_param}")
46
+ end
47
+
48
+ def query_param
49
+ ignore_cache ? '?ignore_cache=true' : ''
50
+ end
51
+
52
+ def version_url
53
+ if api_version == :v3
54
+ ENDPOINTS[api_version] % { application: application, namespace: namespace }
55
+ else
56
+ ENDPOINTS[api_version] % { application: application }
57
+ end
36
58
  end
37
59
  end
@@ -1,7 +1,7 @@
1
1
  module Fb
2
2
  module Jwt
3
3
  class Auth
4
- VERSION = "0.1.0"
4
+ VERSION = "0.4.0"
5
5
  end
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fb-jwt-auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Form builder developers
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-21 00:00:00.000000000 Z
11
+ date: 2021-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jwt
@@ -59,6 +59,7 @@ executables: []
59
59
  extensions: []
60
60
  extra_rdoc_files: []
61
61
  files:
62
+ - ".circleci/config.yml"
62
63
  - ".gitignore"
63
64
  - ".rspec"
64
65
  - ".ruby-version"
@@ -72,6 +73,7 @@ files:
72
73
  - bin/setup
73
74
  - fb-jwt-auth.gemspec
74
75
  - lib/fb/jwt/auth.rb
76
+ - lib/fb/jwt/auth/service_access_token.rb
75
77
  - lib/fb/jwt/auth/service_token_client.rb
76
78
  - lib/fb/jwt/auth/version.rb
77
79
  homepage: https://github.com/ministryofjustice/fb-jwt-auth
@@ -81,7 +83,7 @@ metadata:
81
83
  homepage_uri: https://github.com/ministryofjustice/fb-jwt-auth
82
84
  source_code_uri: https://github.com/ministryofjustice/fb-jwt-auth
83
85
  changelog_uri: https://github.com/ministryofjustice/fb-jwt-auth/blob/main/Changelog.md
84
- post_install_message:
86
+ post_install_message:
85
87
  rdoc_options: []
86
88
  require_paths:
87
89
  - lib
@@ -97,7 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
99
  version: '0'
98
100
  requirements: []
99
101
  rubygems_version: 3.1.4
100
- signing_key:
102
+ signing_key:
101
103
  specification_version: 4
102
104
  summary: JWT authentication done in form builder team
103
105
  test_files: []