authrocket 3.0.0 → 3.1.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: 138d4247bae61aa6ac0c171621a438d8b6d47db7de2a2cc9b7ad61ea7df0784d
4
- data.tar.gz: c83fe483dbdaf4d0e0152972c42e76b17ec5133114cae0b1081c082b74883cee
3
+ metadata.gz: 82ed5a2ea3b1c412a72c84aeac220c4b54e26ca5a0f9cc7248ecbca9e3c211bb
4
+ data.tar.gz: 5acc9665f07016d75af2f26d08527206749cc7e4bfe56df6df248e50a3d9fe5f
5
5
  SHA512:
6
- metadata.gz: 89f83a485b4c382eba642e16d9617bd0107833ee79abe6379fb3126a939f3515becda60182fc886a7976e739f1b45d6a60918ad82dc91cd6960fb8cebb306063
7
- data.tar.gz: d1b32f8ac525755fa7cfed8e06846c6aa6d58e124ba775ecde423901fcc7157db09f65553e27b670f2ab7ec596621d2c033ecbaab2e7da104369955cea2895a2
6
+ metadata.gz: 375bcc4653c32f973da17893ff5250131c655d7122a4ba88a3d818fc454ebfdc7c9561db996c67bb6dec141823dceb1975124b8a0714eae448ac24965ae7d2e0
7
+ data.tar.gz: b12e98876190ded64592edc86c2fd160dd8b72f75086617f72164b3f7aef90f0dc2bb6fc62195eb8e2a0b375bda7f93a1a517fdeff421fbf2b8c0d0912bd47b3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ #### 3.1.0
2
+
3
+ - Automatically handle login tokens in an Authorization header
4
+ eg: Authorization: Bearer {the-token}
5
+ - Optimize LR JWKS support to match by kid
6
+
1
7
  #### 3.0.0
2
8
 
3
9
  - NOTE: This version includes breaking changes.
data/README.md CHANGED
@@ -107,7 +107,7 @@ Sets an application-wide default realm ID. If you're using a single realm, this
107
107
  `AUTHROCKET_URL = https://api-e2.authrocket.com/v2`
108
108
  The URL of the AuthRocket API server. This may vary depending on which cluster your service is provisioned on.
109
109
 
110
- `LOGINROCKET_URL = https://sample.e2.loginrocket.com/`
110
+ `LOGINROCKET_URL = https://SAMPLE.e2.loginrocket.com/`
111
111
  The LoginRocket URL for your Connected App. Used by the streamlined Rails integration (for redirects) and for auto-retrieval of RS256 JWT keys (if AUTHROCKET_JWT_KEY is not set). If your app uses multiple realms, you'll need to handle this on your own. If you're using a custom domain, this will be that domain and will not contain 'loginrocket.com'.
112
112
 
113
113
 
@@ -1,3 +1,3 @@
1
1
  module AuthRocket
2
- VERSION = '3.0.0'
2
+ VERSION = '3.1.0'
3
3
  end
@@ -10,6 +10,14 @@ module AuthRocket::ControllerHelper
10
10
  end
11
11
  end
12
12
 
13
+ def process_authorization_header
14
+ if request.headers['authorization'] =~ %r{Bearer (.+)$}i
15
+ if s = AuthRocket::Session.from_token($1)
16
+ @_current_session = s
17
+ end
18
+ end
19
+ end
20
+
13
21
  def require_login
14
22
  unless current_session
15
23
  redirect_to ar_login_url(redirect_uri: safe_this_uri)
@@ -9,6 +9,7 @@ module AuthRocket
9
9
  include AuthRocket::ControllerHelper
10
10
  helper AuthRocket::ControllerHelper
11
11
  before_action :process_inbound_token
12
+ before_action :process_authorization_header
12
13
  end
13
14
  end
14
15
  end
@@ -19,21 +19,21 @@ module AuthRocket
19
19
  # options - :algo - one of HS256, RS256 (default: auto-detect based on :jwt_key)
20
20
  # - :within - (in seconds) Maximum time since the token was (re)issued
21
21
  # - credentials: {jwt_key: StringOrKey} - used to verify the token
22
+ # returns Session or nil
22
23
  def self.from_token(token, options={})
23
- secret = options.dig(:credentials, :jwt_key) || credentials[:jwt_key]
24
24
  if lr_url = options.dig(:credentials, :loginrocket_url) || credentials[:loginrocket_url]
25
25
  lr_url = lr_url.dup
26
26
  lr_url.concat '/' unless lr_url.ends_with?('/')
27
27
  lr_url.concat 'connect/jwks'
28
28
  end
29
-
30
- algo = options[:algo]
29
+ secret = options.dig(:credentials, :jwt_key) || credentials[:jwt_key]
31
30
  if secret.is_a?(String) && secret.length > 256
32
31
  unless secret.starts_with?('-----BEGIN ')
33
32
  secret = "-----BEGIN PUBLIC KEY-----\n#{secret}\n-----END PUBLIC KEY-----"
34
33
  end
35
34
  secret = OpenSSL::PKey.read secret
36
35
  end
36
+ algo = options[:algo]
37
37
  algo ||= 'RS256' if secret.is_a?(OpenSSL::PKey::RSA)
38
38
  algo ||= 'HS256' if secret
39
39
 
@@ -42,34 +42,22 @@ module AuthRocket
42
42
  raise Error, "Missing jwt_key; set LOGINROCKET_URL, AUTHROCKET_JWT_KEY, or pass in credentials: {loginrocket_url: ...} or {jwt_key: ...}" if secret.blank? && !jwks_eligible
43
43
  return if token.blank?
44
44
 
45
+ base_params = {token: token, within: options[:within], local_creds: options[:credentials]}
45
46
  if jwks_eligible
46
- base_params = {token: token, algo: 'RS256', within: options[:within], local_creds: options[:credentials]}
47
- load_jwk_set(lr_url, use_cached: true).each do |secret|
48
- begin
49
- return parse_jwt secret: secret, **base_params
50
- rescue JWT::DecodeError
51
- end
52
- end
53
- load_jwk_set(lr_url, use_cached: false).each do |secret|
54
- begin
55
- return parse_jwt secret: secret, **base_params
56
- rescue JWT::DecodeError
57
- end
47
+ kid = JSON.parse(JWT::Base64.url_decode(token.split('.')[0]))['kid'] rescue nil
48
+ return if kid.blank?
49
+
50
+ load_jwk_set(lr_url) unless @_jwks[kid]
51
+ if key_set = @_jwks[kid]
52
+ parse_jwt **key_set, **base_params
58
53
  end
59
- nil
60
54
  else
61
- begin
62
- parse_jwt token: token, secret: secret, algo: algo, within: options[:within], local_creds: options[:credentials]
63
- rescue JWT::DecodeError
64
- nil
65
- end
55
+ parse_jwt secret: secret, algo: algo, **base_params
66
56
  end
67
57
  end
68
58
 
69
59
  # private
70
- # raises an exception if eligible for retry using different token
71
- # returns Session on success
72
- # returns nil on a definitive token-parsed-but-invalid
60
+ # returns Session or nil
73
61
  def self.parse_jwt(token:, secret:, algo:, within:, local_creds: nil)
74
62
  opts = {
75
63
  algorithm: algo,
@@ -124,32 +112,16 @@ module AuthRocket
124
112
  }, local_creds)
125
113
 
126
114
  session
127
- rescue JWT::ExpiredSignature, JWT::ImmatureSignature, JWT::InvalidAudError,
128
- JWT::InvalidIatError, JWT::InvalidIssuerError
129
- # successfully parsed, but invalid claims
115
+ rescue JWT::DecodeError
130
116
  nil
131
117
  end
132
118
 
133
119
  @_jwks ||= {}
134
120
  JWKS_MUTEX = Mutex.new
135
- MIN_ATTEMPT_WINDOW = 71 # seconds
136
121
 
137
122
  # private
138
- # use_cached - if there is a cached result, use it regardless of last cache load time
139
- def self.load_jwk_set(uri, use_cached:)
140
- keys, last_time = @_jwks.dig(uri, :keys), @_jwks.dig(uri, :time)
141
- last_time ||= 0
142
-
143
- return keys if use_cached && last_time > 0
144
- return keys if Time.now.to_f - MIN_ATTEMPT_WINDOW < last_time
145
-
123
+ def self.load_jwk_set(uri)
146
124
  JWKS_MUTEX.synchronize do
147
- # recheck in case we locked while being loaded in another process
148
- newer_keys, newer_time = @_jwks.dig(uri, :keys), @_jwks.dig(uri, :time)
149
- newer_time ||= 0
150
-
151
- return newer_keys if newer_time > last_time
152
-
153
125
  path = URI.parse(uri).path
154
126
  headers = build_headers({}, {})
155
127
  rest_opts = {
@@ -164,20 +136,12 @@ module AuthRocket
164
136
  response = execute_request(rest_opts)
165
137
  parsed = parse_response(response)
166
138
  # => {data: json, errors: errors, metadata: metadata}
167
- certs = parsed[:data][:keys].map do |h|
139
+ parsed[:data][:keys].each do |h|
168
140
  crt = "-----BEGIN PUBLIC KEY-----\n#{h['x5c'][0]}\n-----END PUBLIC KEY-----"
169
- OpenSSL::PKey.read crt
170
- end
171
-
172
- @_jwks[uri] = {keys: certs, time: Time.now.to_f}
173
- keys ||= []
174
- just_added = certs - keys
175
- if just_added.any?
176
- just_added
177
- else
178
- certs
141
+ @_jwks[h['kid']] = {secret: OpenSSL::PKey.read(crt), algo: h['alg']}
179
142
  end
180
143
  end
144
+ @_jwks
181
145
  end
182
146
 
183
147
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authrocket
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - AuthRocket Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-22 00:00:00.000000000 Z
11
+ date: 2020-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable