workos 5.28.0 → 5.29.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f536a3eeb0b6d0fc877337104c31dc441ab6f32ccceef45ee7dad60bdba36e99
4
- data.tar.gz: c9a9bb5353eb0077d43f41da09ad95269034f4a81b4504db72ca13f3086f3a29
3
+ metadata.gz: 65fed812ccb7c97df25793369c07af5024b6cb7e1a12675c125bbdf4f3fd5730
4
+ data.tar.gz: b38903b53ed0bad99605ba619453f7fab5653d67baefa0313b6768a81faf0cd6
5
5
  SHA512:
6
- metadata.gz: b689a75850258ff01f9929fdcfccbe8b76c816395d6266053fdabb2515a8c2fcdae43becd6cec2e5ed378cf6f2b6a0176bc38d92111c9bc12a4f44ec8b1b03e6
7
- data.tar.gz: da8fc3b665684df85724b12e8a9c7d86a95fdb26509b92c3e5a13923560c33c8106bc9a65e22d2abe2b7fcf444d3ba67121316b3b2cf75954ce2c51db256c71d
6
+ metadata.gz: 3a06ceb94077a433ac395d1f979209df0a3473f201843c09dc622ebbf389817fc05377556a84ce363d5d9eecf69a8dba9182581ff1b78450cff22cdef4aaf520
7
+ data.tar.gz: 7a746ec3b9930327ad6961da2132be517735f204cc548c4c8c8a36f88db1b85d177daf6225ee947cb90a6b27cc6a0121ba37dd1fea4976f28ad84d11cda84f16
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- workos (5.28.0)
4
+ workos (5.29.0)
5
5
  encryptor (~> 3.0)
6
6
  jwt (~> 2.8)
7
7
 
@@ -29,9 +29,10 @@ module WorkOS
29
29
  end
30
30
 
31
31
  # Authenticates the user based on the session data
32
+ # @param include_expired [Boolean] If true, returns decoded token data even when expired (default: false)
32
33
  # @return [Hash] A hash containing the authentication response and a reason if the authentication failed
33
- # rubocop:disable Metrics/AbcSize
34
- def authenticate
34
+ # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
35
+ def authenticate(include_expired: false)
35
36
  return { authenticated: false, reason: 'NO_SESSION_COOKIE_PROVIDED' } if @session_data.nil?
36
37
 
37
38
  begin
@@ -41,23 +42,41 @@ module WorkOS
41
42
  end
42
43
 
43
44
  return { authenticated: false, reason: 'INVALID_SESSION_COOKIE' } unless session[:access_token]
44
- return { authenticated: false, reason: 'INVALID_JWT' } unless is_valid_jwt(session[:access_token])
45
-
46
- decoded = JWT.decode(session[:access_token], nil, true, algorithms: @jwks_algorithms, jwks: @jwks).first
47
-
48
- {
49
- authenticated: true,
50
- session_id: decoded['sid'],
51
- organization_id: decoded['org_id'],
52
- role: decoded['role'],
53
- roles: decoded['roles'],
54
- permissions: decoded['permissions'],
55
- entitlements: decoded['entitlements'],
56
- feature_flags: decoded['feature_flags'],
57
- user: session[:user],
58
- impersonator: session[:impersonator],
59
- reason: nil,
60
- }
45
+
46
+ begin
47
+ decoded = JWT.decode(
48
+ session[:access_token],
49
+ nil,
50
+ true,
51
+ algorithms: @jwks_algorithms,
52
+ jwks: @jwks,
53
+ verify_expiration: false,
54
+ ).first
55
+
56
+ expired = decoded['exp'] && decoded['exp'] < Time.now.to_i
57
+
58
+ # Early return for expired tokens when not including expired data (backward compatible)
59
+ return { authenticated: false, reason: 'INVALID_JWT' } if expired && !include_expired
60
+
61
+ # Return full data for valid tokens or when include_expired is true
62
+ {
63
+ authenticated: !expired,
64
+ session_id: decoded['sid'],
65
+ organization_id: decoded['org_id'],
66
+ role: decoded['role'],
67
+ roles: decoded['roles'],
68
+ permissions: decoded['permissions'],
69
+ entitlements: decoded['entitlements'],
70
+ feature_flags: decoded['feature_flags'],
71
+ user: session[:user],
72
+ impersonator: session[:impersonator],
73
+ reason: expired ? 'INVALID_JWT' : nil,
74
+ }
75
+ rescue JWT::DecodeError
76
+ { authenticated: false, reason: 'INVALID_JWT' }
77
+ rescue StandardError => e
78
+ { authenticated: false, reason: e.message }
79
+ end
61
80
  end
62
81
 
63
82
  # Refreshes the session data using the refresh token stored in the session data
@@ -66,7 +85,6 @@ module WorkOS
66
85
  # @option options [String] :organization_id The organization ID to use for refreshing the session
67
86
  # @return [Hash] A hash containing a new sealed session, the authentication response,
68
87
  # and a reason if the refresh failed
69
- # rubocop:disable Metrics/PerceivedComplexity
70
88
  def refresh(options = nil)
71
89
  cookie_password = options.nil? || options[:cookie_password].nil? ? @cookie_password : options[:cookie_password]
72
90
 
@@ -168,17 +186,5 @@ module WorkOS
168
186
 
169
187
  jwks
170
188
  end
171
-
172
- # Validates a JWT token using the JWKS set
173
- # @param token [String] The JWT token to validate
174
- # @return [Boolean] True if the token is valid, false otherwise
175
- # rubocop:disable Naming/PredicateName
176
- def is_valid_jwt(token)
177
- JWT.decode(token, nil, true, algorithms: @jwks_algorithms, jwks: @jwks)
178
- true
179
- rescue StandardError
180
- false
181
- end
182
- # rubocop:enable Naming/PredicateName
183
189
  end
184
190
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WorkOS
4
- VERSION = '5.28.0'
4
+ VERSION = '5.29.0'
5
5
  end
@@ -160,6 +160,44 @@ describe WorkOS::Session do
160
160
  expect(result).to eq({ authenticated: false, reason: 'INVALID_JWT' })
161
161
  end
162
162
 
163
+ it 'returns INVALID_JWT without token data when session is expired' do
164
+ session = WorkOS::Session.new(
165
+ user_management: user_management,
166
+ client_id: client_id,
167
+ session_data: session_data,
168
+ cookie_password: cookie_password,
169
+ )
170
+ allow_any_instance_of(JWT::Decode).to receive(:verify_signature).and_return(true)
171
+ allow(Time).to receive(:now).and_return(Time.at(9_999_999_999))
172
+ result = session.authenticate
173
+ expect(result).to eq({ authenticated: false, reason: 'INVALID_JWT' })
174
+ end
175
+
176
+ it 'returns INVALID_JWT with full token data when session is expired and include_expired is true' do
177
+ session = WorkOS::Session.new(
178
+ user_management: user_management,
179
+ client_id: client_id,
180
+ session_data: session_data,
181
+ cookie_password: cookie_password,
182
+ )
183
+ allow_any_instance_of(JWT::Decode).to receive(:verify_signature).and_return(true)
184
+ allow(Time).to receive(:now).and_return(Time.at(9_999_999_999))
185
+ result = session.authenticate(include_expired: true)
186
+ expect(result).to eq({
187
+ authenticated: false,
188
+ session_id: 'session_id',
189
+ organization_id: 'org_id',
190
+ role: 'role',
191
+ roles: ['role'],
192
+ permissions: ['read'],
193
+ feature_flags: nil,
194
+ entitlements: nil,
195
+ user: 'user',
196
+ impersonator: 'impersonator',
197
+ reason: 'INVALID_JWT',
198
+ })
199
+ end
200
+
163
201
  it 'authenticates successfully with valid session_data' do
164
202
  session = WorkOS::Session.new(
165
203
  user_management: user_management,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: workos
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.28.0
4
+ version: 5.29.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - WorkOS
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-11-20 00:00:00.000000000 Z
11
+ date: 2025-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: encryptor