jwt_auth_cognito 0.1.0 → 0.3.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/.rubocop.yml +78 -0
- data/BITBUCKET-DEPLOYMENT.md +290 -0
- data/CHANGELOG.md +76 -0
- data/CLAUDE.md +189 -9
- data/Gemfile +5 -5
- data/README.md +147 -1
- data/Rakefile +108 -5
- data/VERSIONING.md +244 -0
- data/bitbucket-pipelines.yml +266 -0
- data/jwt_auth_cognito.gemspec +43 -36
- data/lib/generators/jwt_auth_cognito/install_generator.rb +25 -25
- data/lib/jwt_auth_cognito/api_key_validator.rb +79 -0
- data/lib/jwt_auth_cognito/configuration.rb +38 -21
- data/lib/jwt_auth_cognito/error_utils.rb +110 -0
- data/lib/jwt_auth_cognito/jwks_service.rb +46 -50
- data/lib/jwt_auth_cognito/jwt_validator.rb +169 -91
- data/lib/jwt_auth_cognito/railtie.rb +3 -3
- data/lib/jwt_auth_cognito/redis_service.rb +90 -51
- data/lib/jwt_auth_cognito/ssm_service.rb +109 -0
- data/lib/jwt_auth_cognito/token_blacklist_service.rb +10 -12
- data/lib/jwt_auth_cognito/user_data_service.rb +332 -0
- data/lib/jwt_auth_cognito/version.rb +2 -2
- data/lib/jwt_auth_cognito.rb +42 -10
- data/lib/tasks/jwt_auth_cognito.rake +69 -70
- metadata +68 -27
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'jwt'
|
4
4
|
|
5
5
|
module JwtAuthCognito
|
6
6
|
class JwtValidator
|
@@ -8,44 +8,108 @@ module JwtAuthCognito
|
|
8
8
|
@config = config
|
9
9
|
@jwks_service = JwksService.new(config)
|
10
10
|
@blacklist_service = TokenBlacklistService.new(config)
|
11
|
+
@api_key_validator = config.enable_api_key_validation ? ApiKeyValidator.new(config) : nil
|
12
|
+
@user_data_service = config.enable_user_data_retrieval ? UserDataService.new(nil, config.user_data_config) : nil
|
13
|
+
@initialized = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize!
|
17
|
+
return if @initialized
|
18
|
+
|
19
|
+
begin
|
20
|
+
@jwks_service.initialize!
|
21
|
+
@blacklist_service.initialize! if @blacklist_service.respond_to?(:initialize!)
|
22
|
+
@user_data_service&.initialize!
|
23
|
+
@initialized = true
|
24
|
+
rescue StandardError => e
|
25
|
+
ErrorUtils.log_error(e, 'JWT Validator initialization failed')
|
26
|
+
raise JwtAuthCognito::ConfigurationError, ErrorUtils::JWT_ERROR_MESSAGES['INITIALIZATION_FAILED']
|
27
|
+
end
|
11
28
|
end
|
12
29
|
|
13
30
|
def validate_token(token, options = {})
|
31
|
+
validate_token_with_api_key(token, nil, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def validate_token_with_api_key(token, api_key = nil, options = {})
|
14
35
|
@config.validate!
|
15
|
-
|
16
|
-
#
|
17
|
-
|
18
|
-
|
36
|
+
|
37
|
+
# Validate API key if provided and enabled
|
38
|
+
api_key_data = nil
|
39
|
+
if api_key && @config.enable_api_key_validation && @api_key_validator
|
40
|
+
api_key_result = @api_key_validator.validate_api_key(api_key)
|
41
|
+
return { valid: false, error: api_key_result[:error] || 'API key validation failed' } unless api_key_result[:valid]
|
42
|
+
|
43
|
+
api_key_data = api_key_result[:key_data]
|
19
44
|
end
|
20
45
|
|
46
|
+
# Check blacklist first
|
47
|
+
return { valid: false, error: 'Token has been revoked' } if @blacklist_service.is_blacklisted?(token)
|
48
|
+
|
21
49
|
# Choose validation method based on configuration
|
22
|
-
case @config.validation_mode
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
50
|
+
result = case @config.validation_mode
|
51
|
+
when :secure
|
52
|
+
validate_token_secure(token, options)
|
53
|
+
when :basic
|
54
|
+
validate_token_basic(token, options)
|
55
|
+
else
|
56
|
+
raise ConfigurationError, "Invalid validation_mode: #{@config.validation_mode}"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Add API key data to result if validation succeeded
|
60
|
+
result[:api_key] = api_key_data if result[:valid] && api_key_data
|
61
|
+
|
62
|
+
result
|
63
|
+
end
|
64
|
+
|
65
|
+
def validate_token_enriched(token, api_key = nil, options = {})
|
66
|
+
# First, perform standard token validation
|
67
|
+
basic_result = validate_token_with_api_key(token, api_key, options)
|
68
|
+
|
69
|
+
# If basic validation fails, return early
|
70
|
+
return basic_result unless basic_result[:valid] && basic_result[:payload]
|
71
|
+
|
72
|
+
# If user data retrieval is not enabled, return basic result
|
73
|
+
return basic_result unless @config.enable_user_data_retrieval && @user_data_service
|
74
|
+
|
75
|
+
# Extract user ID from the token
|
76
|
+
user_id = basic_result[:payload]['sub']
|
77
|
+
unless user_id
|
78
|
+
puts 'Token does not contain sub claim, cannot retrieve user data'
|
79
|
+
return basic_result
|
80
|
+
end
|
81
|
+
|
82
|
+
begin
|
83
|
+
# Get comprehensive user data from Redis
|
84
|
+
user_data = @user_data_service.get_comprehensive_user_data(user_id)
|
85
|
+
|
86
|
+
# Add user data to the result
|
87
|
+
enriched_result = basic_result.dup
|
88
|
+
enriched_result[:user_permissions] = user_data['permissions']
|
89
|
+
enriched_result[:user_organizations] = user_data['organizations']
|
90
|
+
enriched_result[:applications] = user_data['applications']
|
91
|
+
|
92
|
+
enriched_result
|
93
|
+
rescue StandardError => e
|
94
|
+
ErrorUtils.log_error(e, 'User data retrieval failed')
|
95
|
+
# Return basic result even if user data retrieval fails
|
96
|
+
basic_result
|
29
97
|
end
|
30
98
|
end
|
31
99
|
|
32
100
|
def validate_access_token(token)
|
33
101
|
result = validate_token(token)
|
34
|
-
|
35
|
-
if result[:valid] && result[:payload][
|
36
|
-
|
37
|
-
end
|
38
|
-
|
102
|
+
|
103
|
+
return { valid: false, error: 'Token is not an access token' } if result[:valid] && result[:payload]['token_use'] != 'access'
|
104
|
+
|
39
105
|
result
|
40
106
|
end
|
41
107
|
|
42
108
|
def validate_id_token(token)
|
43
109
|
result = validate_token(token)
|
44
|
-
|
45
|
-
if result[:valid] && result[:payload][
|
46
|
-
|
47
|
-
end
|
48
|
-
|
110
|
+
|
111
|
+
return { valid: false, error: 'Token is not an ID token' } if result[:valid] && result[:payload]['token_use'] != 'id'
|
112
|
+
|
49
113
|
result
|
50
114
|
end
|
51
115
|
|
@@ -64,11 +128,34 @@ module JwtAuthCognito
|
|
64
128
|
# Utility methods inspired by Node.js package
|
65
129
|
def extract_token_from_header(authorization_header)
|
66
130
|
return nil unless authorization_header
|
67
|
-
|
131
|
+
|
68
132
|
match = authorization_header.match(/\ABearer (.+)\z/)
|
69
133
|
match ? match[1] : nil
|
70
134
|
end
|
71
135
|
|
136
|
+
def extract_api_key_from_header(api_key_header)
|
137
|
+
# Support common API key header formats
|
138
|
+
return nil unless api_key_header
|
139
|
+
|
140
|
+
api_key_header.strip
|
141
|
+
end
|
142
|
+
|
143
|
+
def extract_api_key_from_headers(headers)
|
144
|
+
# Check various common API key header names (case insensitive)
|
145
|
+
api_key_headers = %w[x-api-key X-API-Key X-API-KEY X-Api-Key]
|
146
|
+
|
147
|
+
api_key_headers.each do |header_name|
|
148
|
+
# Convert headers to a case-insensitive hash for lookup
|
149
|
+
header_key = headers.keys.find { |key| key.downcase == header_name.downcase }
|
150
|
+
next unless header_key
|
151
|
+
|
152
|
+
value = headers[header_key]
|
153
|
+
return extract_api_key_from_header(value) if value
|
154
|
+
end
|
155
|
+
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
|
72
159
|
def decode_token(token)
|
73
160
|
JWT.decode(token, nil, false).first
|
74
161
|
rescue JWT::DecodeError => e
|
@@ -80,15 +167,15 @@ module JwtAuthCognito
|
|
80
167
|
return payload if payload.is_a?(Hash) && payload[:error]
|
81
168
|
|
82
169
|
{
|
83
|
-
sub: payload[
|
84
|
-
username: payload[
|
85
|
-
email: payload[
|
86
|
-
token_use: payload[
|
87
|
-
client_id: payload[
|
88
|
-
issued_at: payload[
|
89
|
-
expires_at: payload[
|
90
|
-
not_before: payload[
|
91
|
-
jti: payload[
|
170
|
+
sub: payload['sub'],
|
171
|
+
username: payload['cognito:username'] || payload['username'],
|
172
|
+
email: payload['email'],
|
173
|
+
token_use: payload['token_use'],
|
174
|
+
client_id: payload['aud'],
|
175
|
+
issued_at: payload['iat'] ? Time.at(payload['iat']) : nil,
|
176
|
+
expires_at: payload['exp'] ? Time.at(payload['exp']) : nil,
|
177
|
+
not_before: payload['nbf'] ? Time.at(payload['nbf']) : nil,
|
178
|
+
jti: payload['jti'],
|
92
179
|
has_client_secret: @config.has_client_secret?
|
93
180
|
}
|
94
181
|
end
|
@@ -107,7 +194,7 @@ module JwtAuthCognito
|
|
107
194
|
payload = decode_token(token)
|
108
195
|
return true if payload.is_a?(Hash) && payload[:error]
|
109
196
|
|
110
|
-
exp = payload[
|
197
|
+
exp = payload['exp']
|
111
198
|
return false unless exp
|
112
199
|
|
113
200
|
Time.now.to_i >= exp
|
@@ -117,18 +204,18 @@ module JwtAuthCognito
|
|
117
204
|
payload = decode_token(token)
|
118
205
|
return nil if payload.is_a?(Hash) && payload[:error]
|
119
206
|
|
120
|
-
exp = payload[
|
207
|
+
exp = payload['exp']
|
121
208
|
return nil unless exp
|
122
209
|
|
123
210
|
seconds = exp - Time.now.to_i
|
124
|
-
seconds
|
211
|
+
seconds.positive? ? seconds : 0
|
125
212
|
end
|
126
213
|
|
127
214
|
# Create a convenience factory method
|
128
215
|
def self.create_cognito_validator(config = nil)
|
129
216
|
if config
|
130
217
|
old_config = JwtAuthCognito.configuration
|
131
|
-
JwtAuthCognito.configure { |
|
218
|
+
JwtAuthCognito.configure { |_c| config }
|
132
219
|
validator = new
|
133
220
|
JwtAuthCognito.instance_variable_set(:@configuration, old_config)
|
134
221
|
validator
|
@@ -142,84 +229,75 @@ module JwtAuthCognito
|
|
142
229
|
def validate_token_secure(token, options = {})
|
143
230
|
# Use JWKS validation for production
|
144
231
|
result = @jwks_service.validate_token_with_jwks(token)
|
145
|
-
|
232
|
+
|
146
233
|
if result[:valid]
|
147
234
|
# Additional custom validations
|
148
235
|
validate_custom_claims(result[:payload], options)
|
149
236
|
end
|
150
|
-
|
237
|
+
|
151
238
|
result
|
152
239
|
end
|
153
240
|
|
154
241
|
def validate_token_basic(token, options = {})
|
155
242
|
# Basic validation without signature verification (development only)
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
end
|
243
|
+
|
244
|
+
payload, = JWT.decode(token, nil, false)
|
245
|
+
|
246
|
+
# Basic claim validation
|
247
|
+
validate_basic_claims(payload)
|
248
|
+
validate_custom_claims(payload, options)
|
249
|
+
|
250
|
+
{
|
251
|
+
valid: true,
|
252
|
+
payload: payload,
|
253
|
+
sub: payload['sub'],
|
254
|
+
username: payload['cognito:username'] || payload['username'],
|
255
|
+
token_use: payload['token_use']
|
256
|
+
}
|
257
|
+
rescue JWT::DecodeError => e
|
258
|
+
{ valid: false, error: "JWT decode error: #{e.message}" }
|
259
|
+
rescue ValidationError => e
|
260
|
+
{ valid: false, error: e.message }
|
261
|
+
rescue StandardError => e
|
262
|
+
{ valid: false, error: "Validation error: #{e.message}" }
|
177
263
|
end
|
178
264
|
|
179
265
|
def validate_basic_claims(payload)
|
180
266
|
now = Time.now.to_i
|
181
|
-
|
267
|
+
|
182
268
|
# Check expiration
|
183
|
-
raise ValidationError,
|
184
|
-
|
269
|
+
raise ValidationError, 'Token has expired' if payload['exp'] && payload['exp'] < now
|
270
|
+
|
185
271
|
# Check issuer
|
186
272
|
expected_issuer = @config.cognito_issuer
|
187
|
-
if payload[
|
188
|
-
|
189
|
-
end
|
190
|
-
|
273
|
+
raise ValidationError, "Invalid issuer. Expected: #{expected_issuer}, got: #{payload['iss']}" if payload['iss'] != expected_issuer
|
274
|
+
|
191
275
|
# Check token use
|
192
|
-
|
193
|
-
|
194
|
-
|
276
|
+
return if %w[access id].include?(payload['token_use'])
|
277
|
+
|
278
|
+
raise ValidationError, 'Invalid token_use claim'
|
195
279
|
end
|
196
280
|
|
197
281
|
def validate_custom_claims(payload, options)
|
198
282
|
# Validate specific user ID if provided
|
199
|
-
if options[:user_id] && payload[
|
200
|
-
|
201
|
-
end
|
202
|
-
|
283
|
+
raise ValidationError, 'Token subject does not match expected user ID' if options[:user_id] && payload['sub'] != options[:user_id]
|
284
|
+
|
203
285
|
# Validate specific client ID if provided
|
204
|
-
if options[:client_id] && payload[
|
205
|
-
|
206
|
-
end
|
207
|
-
|
286
|
+
raise ValidationError, 'Token audience does not match expected client ID' if options[:client_id] && payload['aud'] != options[:client_id]
|
287
|
+
|
208
288
|
# Validate token type if specified
|
209
|
-
if options[:token_use] && payload[
|
210
|
-
|
211
|
-
end
|
212
|
-
|
289
|
+
raise ValidationError, 'Token use does not match expected type' if options[:token_use] && payload['token_use'] != options[:token_use]
|
290
|
+
|
213
291
|
# Custom scope validation
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
292
|
+
return unless options[:required_scopes]
|
293
|
+
|
294
|
+
token_scopes = payload['scope']&.split || []
|
295
|
+
required_scopes = Array(options[:required_scopes])
|
296
|
+
|
297
|
+
missing_scopes = required_scopes - token_scopes
|
298
|
+
return unless missing_scopes.any?
|
299
|
+
|
300
|
+
raise ValidationError, "Token missing required scopes: #{missing_scopes.join(', ')}"
|
223
301
|
end
|
224
302
|
end
|
225
|
-
end
|
303
|
+
end
|
@@ -3,11 +3,11 @@
|
|
3
3
|
module JwtAuthCognito
|
4
4
|
class Railtie < Rails::Railtie
|
5
5
|
rake_tasks do
|
6
|
-
load
|
6
|
+
load 'tasks/jwt_auth_cognito.rake'
|
7
7
|
end
|
8
8
|
|
9
9
|
generators do
|
10
|
-
require
|
10
|
+
require 'generators/jwt_auth_cognito/install_generator'
|
11
11
|
end
|
12
12
|
end
|
13
|
-
end
|
13
|
+
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require 'redis'
|
4
|
+
require 'digest'
|
5
|
+
require 'openssl'
|
6
6
|
|
7
7
|
module JwtAuthCognito
|
8
8
|
class RedisService
|
9
|
-
BLACKLIST_PREFIX =
|
10
|
-
USER_TOKENS_PREFIX =
|
9
|
+
BLACKLIST_PREFIX = 'jwt_blacklist:'
|
10
|
+
USER_TOKENS_PREFIX = 'user_tokens:'
|
11
11
|
|
12
12
|
def initialize(config = JwtAuthCognito.configuration)
|
13
13
|
@config = config
|
@@ -17,13 +17,13 @@ module JwtAuthCognito
|
|
17
17
|
def save_revoked_token(token_id, ttl = nil)
|
18
18
|
connect_redis
|
19
19
|
key = "#{BLACKLIST_PREFIX}#{token_id}"
|
20
|
-
|
20
|
+
|
21
21
|
if ttl
|
22
|
-
@redis.setex(key, ttl,
|
22
|
+
@redis.setex(key, ttl, 'revoked')
|
23
23
|
else
|
24
|
-
@redis.set(key,
|
24
|
+
@redis.set(key, 'revoked')
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
true
|
28
28
|
rescue Redis::BaseError => e
|
29
29
|
raise BlacklistError, "Failed to save revoked token: #{e.message}"
|
@@ -33,8 +33,8 @@ module JwtAuthCognito
|
|
33
33
|
connect_redis
|
34
34
|
key = "#{BLACKLIST_PREFIX}#{token_id}"
|
35
35
|
result = @redis.exists?(key)
|
36
|
-
result.is_a?(Integer) ? result
|
37
|
-
rescue Redis::BaseError
|
36
|
+
result.is_a?(Integer) ? result.positive? : result
|
37
|
+
rescue Redis::BaseError
|
38
38
|
# Graceful degradation - if Redis is down, don't block validation
|
39
39
|
false
|
40
40
|
end
|
@@ -50,19 +50,19 @@ module JwtAuthCognito
|
|
50
50
|
|
51
51
|
def invalidate_user_tokens(user_id)
|
52
52
|
connect_redis
|
53
|
-
|
53
|
+
|
54
54
|
# Get all tokens for the user
|
55
55
|
user_key = "#{USER_TOKENS_PREFIX}#{user_id}"
|
56
56
|
token_ids = @redis.smembers(user_key)
|
57
|
-
|
57
|
+
|
58
58
|
# Add all tokens to blacklist
|
59
59
|
token_ids.each do |token_id|
|
60
60
|
save_revoked_token(token_id)
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
# Clear the user's token set
|
64
64
|
@redis.del(user_key)
|
65
|
-
|
65
|
+
|
66
66
|
token_ids.length
|
67
67
|
rescue Redis::BaseError => e
|
68
68
|
raise BlacklistError, "Failed to invalidate user tokens: #{e.message}"
|
@@ -70,28 +70,28 @@ module JwtAuthCognito
|
|
70
70
|
|
71
71
|
def track_user_token(user_id, token_id, ttl = nil)
|
72
72
|
connect_redis
|
73
|
-
|
73
|
+
|
74
74
|
user_key = "#{USER_TOKENS_PREFIX}#{user_id}"
|
75
75
|
@redis.sadd(user_key, token_id)
|
76
|
-
|
76
|
+
|
77
77
|
# Set expiration on the user's token set
|
78
78
|
@redis.expire(user_key, ttl) if ttl
|
79
|
-
|
79
|
+
|
80
80
|
true
|
81
|
-
rescue Redis::BaseError
|
81
|
+
rescue Redis::BaseError
|
82
82
|
# Non-critical operation, log but don't fail
|
83
83
|
false
|
84
84
|
end
|
85
85
|
|
86
86
|
def generate_token_id(token)
|
87
87
|
# Try to extract jti from token first
|
88
|
-
begin
|
88
|
+
begin
|
89
89
|
payload = JWT.decode(token, nil, false).first
|
90
|
-
return payload[
|
90
|
+
return payload['jti'] if payload['jti']
|
91
91
|
rescue JWT::DecodeError
|
92
92
|
# Fall back to hash if token can't be decoded
|
93
93
|
end
|
94
|
-
|
94
|
+
|
95
95
|
# Generate hash-based ID
|
96
96
|
Digest::SHA256.hexdigest(token)[0, 16]
|
97
97
|
end
|
@@ -102,23 +102,21 @@ module JwtAuthCognito
|
|
102
102
|
return @redis if @redis
|
103
103
|
|
104
104
|
redis_options = build_redis_options
|
105
|
-
|
105
|
+
|
106
106
|
# Retry logic with exponential backoff (similar to Node.js implementation)
|
107
107
|
max_retries = 3
|
108
108
|
retry_count = 0
|
109
|
-
|
109
|
+
|
110
110
|
begin
|
111
111
|
@redis = Redis.new(redis_options)
|
112
112
|
@redis.ping # Test connection
|
113
113
|
@redis
|
114
114
|
rescue Redis::BaseError => e
|
115
115
|
retry_count += 1
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
raise BlacklistError, "Failed to connect to Redis after #{max_retries} retries: #{e.message}"
|
121
|
-
end
|
116
|
+
raise BlacklistError, "Failed to connect to Redis after #{max_retries} retries: #{e.message}" unless retry_count <= max_retries
|
117
|
+
|
118
|
+
sleep(0.1 * (2**retry_count)) # Exponential backoff
|
119
|
+
retry
|
122
120
|
end
|
123
121
|
end
|
124
122
|
|
@@ -145,35 +143,76 @@ module JwtAuthCognito
|
|
145
143
|
|
146
144
|
def build_ssl_params
|
147
145
|
ssl_params = {}
|
148
|
-
|
146
|
+
|
149
147
|
# Set TLS version constraints
|
150
|
-
if @config.redis_tls_min_version
|
151
|
-
|
148
|
+
ssl_params[:min_version] = parse_tls_version(@config.redis_tls_min_version) if @config.redis_tls_min_version
|
149
|
+
|
150
|
+
ssl_params[:max_version] = parse_tls_version(@config.redis_tls_max_version) if @config.redis_tls_max_version
|
151
|
+
|
152
|
+
# CA certificate configuration with multiple sources
|
153
|
+
ca_cert_data = load_ca_certificate
|
154
|
+
if ca_cert_data
|
155
|
+
# Create a temporary file for the CA certificate
|
156
|
+
require 'tempfile'
|
157
|
+
temp_file = Tempfile.new('redis_ca_cert')
|
158
|
+
temp_file.write(ca_cert_data)
|
159
|
+
temp_file.close
|
160
|
+
ssl_params[:ca_file] = temp_file.path
|
161
|
+
|
162
|
+
# Store reference to prevent garbage collection
|
163
|
+
@temp_ca_file = temp_file
|
152
164
|
end
|
153
|
-
|
154
|
-
|
155
|
-
|
165
|
+
|
166
|
+
# Verification mode
|
167
|
+
ssl_params[:verify_mode] = case @config.redis_verify_mode
|
168
|
+
when 'none'
|
169
|
+
OpenSSL::SSL::VERIFY_NONE
|
170
|
+
when 'peer'
|
171
|
+
OpenSSL::SSL::VERIFY_PEER
|
172
|
+
else
|
173
|
+
OpenSSL::SSL::VERIFY_PEER
|
174
|
+
end
|
175
|
+
|
176
|
+
ssl_params
|
177
|
+
end
|
178
|
+
|
179
|
+
def load_ca_certificate
|
180
|
+
# Priority order for certificate loading (matching Node.js implementation):
|
181
|
+
# 1. SSM Parameter Store (for auth-service compatibility)
|
182
|
+
# 2. Local file system
|
183
|
+
# 3. Environment variable
|
184
|
+
|
185
|
+
# 1. Try SSM Parameter Store first (for auth-service compatibility)
|
186
|
+
if @config.redis_ca_cert_ssm_path && @config.redis_ca_cert_ssm_name
|
187
|
+
begin
|
188
|
+
puts '🔍 Loading CA certificate from SSM...'
|
189
|
+
return JwtAuthCognito::SSMService.get_ca_certificate(
|
190
|
+
@config.redis_ca_cert_ssm_path,
|
191
|
+
@config.redis_ca_cert_ssm_name
|
192
|
+
)
|
193
|
+
rescue StandardError => e
|
194
|
+
puts "⚠️ Failed to load certificate from SSM: #{e.message}"
|
195
|
+
puts '⚠️ Falling back to file system...'
|
196
|
+
end
|
156
197
|
end
|
157
|
-
|
158
|
-
#
|
198
|
+
|
199
|
+
# 2. Try local file system
|
159
200
|
if @config.redis_ca_cert_path && @config.redis_ca_cert_name
|
160
201
|
ca_cert_file = File.join(@config.redis_ca_cert_path, @config.redis_ca_cert_name)
|
161
202
|
if File.exist?(ca_cert_file)
|
162
|
-
|
203
|
+
puts "📁 Loading CA certificate from file system: #{ca_cert_file}"
|
204
|
+
return File.read(ca_cert_file)
|
163
205
|
end
|
164
206
|
end
|
165
|
-
|
166
|
-
#
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
when 'peer'
|
171
|
-
ssl_params[:verify_mode] = OpenSSL::SSL::VERIFY_PEER
|
172
|
-
else
|
173
|
-
ssl_params[:verify_mode] = OpenSSL::SSL::VERIFY_PEER
|
207
|
+
|
208
|
+
# 3. Try environment variable
|
209
|
+
if ENV['REDIS_CA_CERT']
|
210
|
+
puts '🌍 Loading CA certificate from environment variable'
|
211
|
+
return ENV['REDIS_CA_CERT']
|
174
212
|
end
|
175
|
-
|
176
|
-
|
213
|
+
|
214
|
+
puts '⚠️ No CA certificate found, proceeding without certificate validation'
|
215
|
+
nil
|
177
216
|
end
|
178
217
|
|
179
218
|
def parse_tls_version(version_string)
|
@@ -191,4 +230,4 @@ module JwtAuthCognito
|
|
191
230
|
end
|
192
231
|
end
|
193
232
|
end
|
194
|
-
end
|
233
|
+
end
|