fb-jwt-auth 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +63 -0
- data/.gitignore +1 -0
- data/Changelog.md +18 -0
- data/README.md +29 -1
- data/lib/fb/jwt/auth.rb +56 -22
- data/lib/fb/jwt/auth/service_access_token.rb +45 -0
- data/lib/fb/jwt/auth/service_token_client.rb +26 -4
- data/lib/fb/jwt/auth/version.rb +1 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45ebbebff675bd2afe201e48c9d3d711ae255619511e00318f6cd39aef8094c0
|
4
|
+
data.tar.gz: e1d51f15d8b8a4a55be59228edce5aa3723b74bd312d0b87f868e81ecd3bcdb3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/Changelog.md
CHANGED
@@ -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
|
data/lib/fb/jwt/auth.rb
CHANGED
@@ -1,24 +1,23 @@
|
|
1
1
|
require 'fb/jwt/auth/version'
|
2
2
|
require 'openssl'
|
3
3
|
require 'jwt'
|
4
|
-
require 'active_support/
|
4
|
+
require 'active_support/all'
|
5
5
|
|
6
6
|
module Fb
|
7
7
|
module Jwt
|
8
8
|
class Auth
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
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.
|
47
|
+
raise TokenNotPresentError.new('Token is not present') if token.blank?
|
48
|
+
|
49
|
+
application_details = find_application_info
|
43
50
|
|
44
51
|
begin
|
45
|
-
|
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 = "
|
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}
|
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
|
75
|
-
|
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
|
-
|
8
|
+
ENDPOINTS = {
|
9
|
+
v2: '/service/v2/%{application}',
|
10
|
+
v3: '/v3/applications/%{application}/namespaces/%{namespace}'
|
11
|
+
}
|
9
12
|
|
10
|
-
|
11
|
-
|
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(
|
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
|
data/lib/fb/jwt/auth/version.rb
CHANGED
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.
|
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:
|
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: []
|