authress-sdk 2.0.41.0 → 2.0.43.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: f455223c994f32796ba78600e80315534d15e06b1aeef0012738159eb0a995fc
4
- data.tar.gz: 478cfab70f9202188d0723db8a20579731733661e9672a3f048dfaf9c4d385fb
3
+ metadata.gz: 8d32ad65dcb072b5d8a7365e5859fc3342ae2425945380781707650a19f9926c
4
+ data.tar.gz: f81db7d0bc6e7fd9c5d1217f6cd87d02a3ce8dffd8e8e69e0ae95109ab33944c
5
5
  SHA512:
6
- metadata.gz: 218b7773c304ad04d04de5a81a9688b3e91273b0a72ea2441dbcca268406c5d528b473d022fa3352e5d74d67889a4997b356479b8af189c9988d2faa19a0758f
7
- data.tar.gz: 5187f86eee213a864314303ef2345d690e81f48cad3774d295d69e681cb52054f1c0504de0270f9eb1871b27fb1efd98159e7aaf47c2503ad3ee2faeb1b3d888
6
+ metadata.gz: 26e8f74c75866011ca9462cf00388401a1d7e402925013e588aaed065cebd8ff6662ce5fe95ce9d98dcdc4d69693e711a862566b1b4c04120b30ab31700b8c62
7
+ data.tar.gz: e7aacd4c91af4346b2c3ae79687335e5394fa811d2ceca575ddd531f9789dc9dddfffe2162cce941e1a4025b1228047247130141a3bb26930387f8325040a16f
data/README.md CHANGED
@@ -78,7 +78,7 @@ end
78
78
 
79
79
  # on api route
80
80
  [route('/resources/<resourceId>')]
81
- def getResource(resourceId) {
81
+ def getResource(resourceId)
82
82
  # Check Authress to authorize the user
83
83
  user_identity = AuthressSdk::AuthressClient.verify_token(request.headers.get('authorization'))
84
84
 
@@ -86,6 +86,7 @@ def getResource(resourceId) {
86
86
  user_id = user_identity.sub
87
87
  resource_uri = "resources/#{resourceId}" # String | The uri path of a resource to validate, must be URL encoded, uri segments are allowed, the resource must be a full path, and permissions are not inherited by sub-resources.
88
88
  permission = 'READ' # String | Permission to check, '*' and scoped permissions can also be checked here.
89
+
89
90
  begin
90
91
  # Check to see if a user has permissions to a resource.
91
92
  api_instance = AuthressSdk::UserPermissionsApi.new
@@ -20,6 +20,9 @@ module AuthressSdk
20
20
  # Token Provider
21
21
  attr_accessor :token_provider
22
22
 
23
+ # The Token verifier
24
+ attr_accessor :token_verifier
25
+
23
26
  # Initializes the AuthressClient
24
27
  def initialize()
25
28
  @config = {
@@ -29,6 +32,7 @@ module AuthressSdk
29
32
  }
30
33
 
31
34
  @token_provider = ConstantTokenProvider.new(nil)
35
+ @token_verifier = TokenVerifier.new()
32
36
  end
33
37
 
34
38
  def self.default
@@ -297,5 +301,12 @@ module AuthressSdk
297
301
  obj
298
302
  end
299
303
  end
304
+
305
+ # Verify a JWT token
306
+ # @param [String] The JWT token
307
+ # @return [Object] Returns a Map of user identity properties
308
+ def verify_token(token)
309
+ @token_verifier.verify_token(custom_domain_url, token)
310
+ end
300
311
  end
301
312
  end
@@ -146,7 +146,7 @@ module OmniAuth
146
146
  env['omniauth.auth'] = auth_hash
147
147
  call_app!
148
148
  end
149
- rescue AuthressSdk::TokenValidationError => e
149
+ rescue AuthressSdk::TokenVerificationError => e
150
150
  fail!(:token_validation_error, e)
151
151
  rescue ::OAuth2::Error, CallbackError => e
152
152
  fail!(:invalid_credentials, e)
@@ -1,17 +1,90 @@
1
- require 'date'
1
+ require 'time'
2
2
  require 'json'
3
3
  require 'logger'
4
4
  require 'uri'
5
5
 
6
6
  module AuthressSdk
7
7
  class ServiceClientTokenProvider
8
- def initialize(client_access_key)
8
+ def initialize(client_access_key, custom_domain_url = nil)
9
+ @custom_domain_url = custom_domain_url
9
10
  @client_access_key = client_access_key
11
+ @cachedKeyData = nil
12
+ end
13
+
14
+ def sanitizeUrl(url)
15
+ if url.nil?
16
+ return nil
17
+ end
18
+
19
+ if (url.match(/^http/))
20
+ return url
21
+ end
22
+
23
+ if (url.match(/^localhost/))
24
+ return "http://#{url}"
25
+ end
26
+
27
+ return "https://#{url}"
28
+ end
29
+
30
+ def get_issuer(unsanitizedAuthressCustomDomain, decodedAccessKey)
31
+ authressCustomDomain = sanitizeUrl(@custom_domain_url).gsub(/\/+$/, '')
32
+ return "#{authressCustomDomain}/v1/clients/#{decodedAccessKey.clientId}"
10
33
  end
11
34
 
12
35
  def get_token()
13
- # TODO: This should use the JWT creation strategy and not the client api token one
14
- @client_access_key
36
+ if @cachedKeyData && @cachedKeyData.token && Time.now().to_i() + 3600 < @cachedKeyData.expiresAtInSeconds
37
+ return @cachedKeyData.token
38
+ end
39
+
40
+ accountId = @client_access_key.split('.')[2];
41
+ decodedAccessKeyHash = {
42
+ clientId: @client_access_key.split('.')[0],
43
+ keyId: @client_access_key.split('.')[1],
44
+ audience: "#{accountId}.accounts.authress.io",
45
+ privateKey: @client_access_key.split('.')[3]
46
+ }
47
+ decodedAccessKey = Struct.new(*decodedAccessKeyHash.keys).new(*decodedAccessKeyHash.values)
48
+
49
+ now = Time.now().to_i()
50
+ jwt = {
51
+ aud: decodedAccessKey.audience,
52
+ iss: get_issuer(@custom_domain_url || "#{accountId}.api.authress.io", decodedAccessKey),
53
+ sub: decodedAccessKey.clientId,
54
+ client_id: decodedAccessKey.clientId,
55
+ iat: now,
56
+ # valid for 24 hours
57
+ exp: now + 60 * 60 * 24,
58
+ scope: 'openid'
59
+ }
60
+
61
+ if decodedAccessKey.privateKey.nil?
62
+ raise Exception("Invalid Service Client Access Key")
63
+ end
64
+
65
+ return decodedAccessKey.privateKey
66
+
67
+ # The Ed25519 module is broken right now and doesn't accept valid private keys.
68
+ # private_key = RbNaCl::Signatures::Ed25519::SigningKey.new(Base64.decode64(decodedAccessKey.privateKey)[0, 32])
69
+
70
+ # token = JWT.encode(jwt, private_key, 'ED25519', { typ: 'at+jwt', alg: 'EdDSA', kid: decodedAccessKey.keyId })
71
+ # @cachedKeyData = { token: token, expires: jwt['exp'] }
72
+ # return token
15
73
  end
16
74
  end
17
75
  end
76
+
77
+ module JWTExtensions
78
+ # Fixed because https://github.com/jwt/ruby-jwt/issues/334 is still broken
79
+ def encode_header
80
+ # https://github.com/jwt/ruby-jwt/blob/main/lib/jwt/encode.rb#L17
81
+ @headers["alg"] = @headers["alg"].downcase == "ed25519" ? "EdDSA" : @headers["alg"]
82
+ super
83
+ end
84
+ end
85
+
86
+ module JWT
87
+ class Encode
88
+ prepend JWTExtensions
89
+ end
90
+ end
@@ -1,13 +1,113 @@
1
1
  require 'base64'
2
2
  require 'uri'
3
3
  require 'json'
4
+ require 'jwt'
4
5
 
5
6
  module AuthressSdk
6
- class TokenValidationError < StandardError
7
- attr_reader :error_reason
8
- def initialize(msg)
9
- @error_reason = msg
10
- super(msg)
7
+ class TokenVerifier
8
+
9
+ attr_accessor :key_map
10
+
11
+ def initialize()
12
+ @key_map = {}
13
+ end
14
+
15
+ def verify_token(authressCustomDomain, token)
16
+ sanitized_domain = authressCustomDomain.gsub(/https?:\/\//, '')
17
+ completeIssuerUrl = "https://#{sanitized_domain}"
18
+ if token.nil?
19
+ raise TokenVerificationError.new("Unauthorized: No token specified")
20
+ end
21
+
22
+ begin
23
+ authenticationToken = token
24
+ unverifiedPayload = JWT.decode(authenticationToken, nil, false)
25
+ rescue JWT::DecodeError
26
+ begin
27
+ serviceClient = AuthressSdk::ServiceClientTokenProvider.new(token, completeIssuerUrl)
28
+ authenticationToken = serviceClient.get_token()
29
+ unverifiedPayload = JWT.decode(authenticationToken, nil, false)
30
+ rescue Exception => e
31
+ raise TokenVerificationError.new("Unauthorized: Invalid Token format: #{e}")
32
+ end
33
+ end
34
+
35
+ if unverifiedPayload.nil?
36
+ raise TokenVerificationError.new("Unauthorized: Invalid Token or Token not found")
37
+ end
38
+
39
+ kid = unverifiedPayload[1]["kid"]
40
+ if kid.nil?
41
+ raise TokenVerificationError.new("Unauthorized: No KID found in token")
42
+ end
43
+
44
+ issuer = unverifiedPayload[0]["iss"]
45
+ if issuer.nil?
46
+ raise TokenVerificationError.new("Unauthorized: No Issuer in token")
47
+ end
48
+
49
+ if (URI(issuer).host != URI(completeIssuerUrl).host)
50
+ raise TokenVerificationError.new("Unauthorized: Issuer does not match")
51
+ end
52
+
53
+ # Handle service client checking
54
+ issuerPath = URI(issuer).path
55
+ clientIdMatcher = /^\/v\d\/clients\/([^\/]+)$/.match(issuerPath)
56
+ if clientIdMatcher && clientIdMatcher[1] != unverifiedPayload[0]['sub']
57
+ raise TokenVerificationError.new("Unauthorized: Service ID does not match token sub claim")
58
+ end
59
+
60
+ jwkObject = get_public_key("#{issuer}/.well-known/openid-configuration/jwks", kid)
61
+ jwk = jwkObject.verify_key()
62
+
63
+ begin
64
+ # https://github.com/jwt/ruby-jwt?tab=readme-ov-file
65
+ decodedResult = JWT.decode(authenticationToken, jwk, true, { algorithm: 'EdDSA' })
66
+ return decodedResult[0]
67
+ rescue Exception => e
68
+ raise TokenVerificationError.new("Unauthorized: Token is invalid - #{e}")
69
+ end
70
+ end
71
+
72
+ def get_public_key(jwkKeyListUrl, kid)
73
+ hashKey = "#{jwkKeyListUrl}|#{kid}"
74
+
75
+ if @key_map[hashKey].nil?
76
+ @key_map[hashKey] = get_key_uncached(jwkKeyListUrl, kid)
77
+ end
78
+
79
+ begin
80
+ key = @key_map[hashKey]
81
+ return key
82
+ rescue
83
+ @key_map[hashKey] = get_key_uncached(jwkKeyListUrl, kid)
84
+ return @key_map[hashKey]
85
+ end
11
86
  end
87
+
88
+ def get_key_uncached(jwkKeyListUrl, kid)
89
+ response = Typhoeus::Request.new(jwkKeyListUrl.to_s, { :method => :get, :ssl_verifypeer => true, :ssl_verifyhost => 2, :verbose => false }).run
90
+ unless response.success?
91
+ raise TokenVerificationError.new("Unauthorized: Failed to fetch jwks from: #{jwkKeyListUrl}")
92
+ end
93
+
94
+ jwks = JWT::JWK::Set.new(JSON.parse(response.body))
95
+
96
+ key = jwks.find{|key| key[:kid] == kid }
97
+ if key
98
+ return key
99
+ end
100
+
101
+ raise TokenVerificationError.new("Unauthorized: KID was not found in the list of valid JWKs: #{kid}")
102
+ end
103
+
104
+ class TokenVerificationError < StandardError
105
+ attr_reader :error_reason
106
+ def initialize(msg)
107
+ @error_reason = msg
108
+ super(msg)
109
+ end
110
+ end
111
+
12
112
  end
13
113
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authress-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.41.0
4
+ version: 2.0.43.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Authress
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-21 00:00:00.000000000 Z
11
+ date: 2024-05-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: typhoeus
@@ -60,6 +60,20 @@ dependencies:
60
60
  version: '0'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: jwt
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '2.8'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '2.8'
75
+ - !ruby/object:Gem::Dependency
76
+ name: oauth2
63
77
  requirement: !ruby/object:Gem::Requirement
64
78
  requirements:
65
79
  - - ">="
@@ -73,7 +87,7 @@ dependencies:
73
87
  - !ruby/object:Gem::Version
74
88
  version: '0'
75
89
  - !ruby/object:Gem::Dependency
76
- name: oauth2
90
+ name: rbnacl
77
91
  requirement: !ruby/object:Gem::Requirement
78
92
  requirements:
79
93
  - - ">="