workos 7.1.0 → 7.1.1

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: d161e036b799ab7a5bce02845205f97c94ea6a2307fb577e9b5895fe831233b1
4
- data.tar.gz: 2179efae26f51148faa9419595486310ccc4b2bdf39c9730107250b4ea0a1d52
3
+ metadata.gz: 784d66731f3736d0a82674d067f89b258f5979c497ae57bbf0d413af3fa3cd61
4
+ data.tar.gz: 7fed939e2d52cf44085df0a6aeb3d7cad07778eefff07c1380b023ee7810624c
5
5
  SHA512:
6
- metadata.gz: 6a21b54d6f946d776b57d6f6d5e4441a9c9ea2b4367111d1133bad75a06681f506a451da04f0edc03acf42269c1444f0e889927d9a8ac2db6a0af6ec862a3a0d
7
- data.tar.gz: 7641dd7a2a32dd65068185dbb5b3b65e17cce535f96b324439c9a0496f757c601ae97d3a5e24d007ed30d842c4f3ac5c76baeddbd97466277ec44a15af7fc92c
6
+ metadata.gz: 0e75fe9404664c71cf3dc2fa392c0871406cbcc9aca8b0e2b87f09c88e894b40a8485a79d45e1feb2df068ff2a93d204a83ff331ae0c5a6c4d1eccb601021b98
7
+ data.tar.gz: 5504f24a552331b35908cf14dc1cdd54023ef646fad049ccb7ebe1e95a1f71c24c4f5f8ef0cda714ecdd795c34dbbda2fafcb898cbc93099ce61e6aff215712b
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "7.1.0"
2
+ ".": "7.1.1"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [7.1.1](https://github.com/workos/workos-ruby/compare/v7.1.0...v7.1.1) (2026-04-29)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * seal session client-side in Session#refresh ([#470](https://github.com/workos/workos-ruby/issues/470)) ([32662ab](https://github.com/workos/workos-ruby/commit/32662ab3d67ffdcc895141aa8fd5efb22ba79fdb))
9
+
3
10
  ## [7.1.0](https://github.com/workos/workos-ruby/compare/v7.0.0...v7.1.0) (2026-04-27)
4
11
 
5
12
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- workos (7.1.0)
4
+ workos (7.1.1)
5
5
  jwt (~> 3.1)
6
6
  logger (~> 1.7)
7
7
  zeitwerk (~> 2.6)
@@ -124,7 +124,7 @@ CHECKSUMS
124
124
  unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
125
125
  unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
126
126
  webmock (3.26.2) sha256=774556f2ea6371846cca68c01769b2eac0d134492d21f6d0ab5dd643965a4c90
127
- workos (7.1.0)
127
+ workos (7.1.1)
128
128
  zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd
129
129
 
130
130
  BUNDLED WITH
@@ -90,18 +90,27 @@ module WorkOS
90
90
  body = {
91
91
  "grant_type" => "refresh_token",
92
92
  "client_id" => @client.client_id,
93
- "refresh_token" => session["refresh_token"],
94
- "session" => {"seal_session" => true, "cookie_password" => effective_password}
93
+ "refresh_token" => session["refresh_token"]
95
94
  }
96
95
  body["organization_id"] = organization_id if organization_id
97
96
 
98
97
  response = @client.request(method: :post, path: "/user_management/authenticate", auth: true, body: body)
99
98
  auth_response = JSON.parse(response.body)
100
- sealed = auth_response["sealed_session"].to_s
101
- @seal_data = sealed
102
- @cookie_password = effective_password
103
99
 
100
+ sealed = @manager.seal_session_from_auth_response(
101
+ access_token: auth_response["access_token"],
102
+ refresh_token: auth_response["refresh_token"],
103
+ cookie_password: effective_password,
104
+ user: auth_response["user"],
105
+ impersonator: auth_response["impersonator"]
106
+ )
107
+
108
+ # Decode before mutating session state so a malformed access_token
109
+ # doesn't leave the Session half-updated.
104
110
  decoded = @manager.decode_jwt(auth_response["access_token"])
111
+
112
+ @seal_data = sealed
113
+ @cookie_password = effective_password
105
114
  SessionManager::RefreshSuccess.new(
106
115
  authenticated: true,
107
116
  sealed_session: sealed,
@@ -117,6 +126,8 @@ module WorkOS
117
126
  )
118
127
  rescue WorkOS::AuthenticationError, WorkOS::InvalidRequestError => e
119
128
  SessionManager::RefreshError.new(authenticated: false, reason: e.message)
129
+ rescue JWT::DecodeError => e
130
+ SessionManager::RefreshError.new(authenticated: false, reason: e.message)
120
131
  end
121
132
 
122
133
  # Build the WorkOS session-logout URL for the currently authenticated session.
@@ -2,5 +2,5 @@
2
2
 
3
3
  # @oagen-ignore-file
4
4
  module WorkOS
5
- VERSION = "7.1.0"
5
+ VERSION = "7.1.1"
6
6
  end
@@ -206,6 +206,131 @@ class SessionTest < Minitest::Test
206
206
  assert_equal "https://app/cb", params["return_to"]
207
207
  end
208
208
 
209
+ # --- Session#refresh -------------------------------------------------------
210
+
211
+ def test_refresh_seals_session_client_side_and_returns_refresh_success
212
+ rsa, pub = signing_key_pair
213
+ old_access = make_jwt({"sid" => "session_old", "exp" => Time.now.to_i - 60}, rsa)
214
+ sealed = @sm.seal_data({"access_token" => old_access, "refresh_token" => "rt_old", "user" => {"id" => "u_1"}}, PASSWORD)
215
+
216
+ new_access = make_jwt({"sid" => "session_new", "org_id" => "org_1", "role" => "admin", "exp" => Time.now.to_i + 300}, rsa)
217
+ api_response = {
218
+ "access_token" => new_access,
219
+ "refresh_token" => "rt_new",
220
+ "user" => {"id" => "u_1", "email" => "a@b.com"},
221
+ "impersonator" => nil
222
+ }
223
+
224
+ stub_request(:post, "https://api.workos.com/user_management/authenticate")
225
+ .with(body: hash_including("grant_type" => "refresh_token", "refresh_token" => "rt_old"))
226
+ .to_return(status: 200, body: api_response.to_json)
227
+ stub_request(:get, "https://api.workos.com/sso/jwks/client_001")
228
+ .to_return(status: 200, body: jwks_payload(pub).to_json)
229
+
230
+ session = @sm.load(seal_data: sealed, cookie_password: PASSWORD)
231
+ result = session.refresh
232
+
233
+ assert_kind_of WorkOS::SessionManager::RefreshSuccess, result
234
+ assert result.authenticated
235
+ assert_equal "session_new", result.session_id
236
+ assert_equal "org_1", result.organization_id
237
+ assert_equal "admin", result.role
238
+ assert_equal "u_1", result.user["id"]
239
+
240
+ # sealed_session should be a non-empty string that round-trips
241
+ refute_empty result.sealed_session
242
+ unsealed = @sm.unseal_data(result.sealed_session, PASSWORD)
243
+ assert_equal new_access, unsealed["access_token"]
244
+ assert_equal "rt_new", unsealed["refresh_token"]
245
+ end
246
+
247
+ def test_refresh_updates_internal_seal_data_for_subsequent_authenticate
248
+ rsa, pub = signing_key_pair
249
+ old_access = make_jwt({"sid" => "session_old", "exp" => Time.now.to_i - 60}, rsa)
250
+ sealed = @sm.seal_data({"access_token" => old_access, "refresh_token" => "rt_old", "user" => {"id" => "u_1"}}, PASSWORD)
251
+
252
+ new_access = make_jwt({"sid" => "session_refreshed", "org_id" => "org_2", "exp" => Time.now.to_i + 300}, rsa)
253
+ api_response = {
254
+ "access_token" => new_access,
255
+ "refresh_token" => "rt_new",
256
+ "user" => {"id" => "u_1"}
257
+ }
258
+
259
+ stub_request(:post, "https://api.workos.com/user_management/authenticate")
260
+ .to_return(status: 200, body: api_response.to_json)
261
+ stub_request(:get, "https://api.workos.com/sso/jwks/client_001")
262
+ .to_return(status: 200, body: jwks_payload(pub).to_json)
263
+
264
+ session = @sm.load(seal_data: sealed, cookie_password: PASSWORD)
265
+ session.refresh
266
+
267
+ # A subsequent authenticate should use the refreshed token
268
+ auth = session.authenticate
269
+ assert_kind_of WorkOS::SessionManager::AuthSuccess, auth
270
+ assert auth.authenticated
271
+ assert_equal "session_refreshed", auth.session_id
272
+ end
273
+
274
+ def test_refresh_returns_error_on_invalid_cookie
275
+ result = @sm.refresh(seal_data: "garbage", cookie_password: PASSWORD)
276
+ assert_kind_of WorkOS::SessionManager::RefreshError, result
277
+ refute result.authenticated
278
+ assert_equal WorkOS::SessionManager::INVALID_SESSION_COOKIE, result.reason
279
+ end
280
+
281
+ def test_refresh_returns_error_when_no_refresh_token
282
+ sealed = @sm.seal_data({"access_token" => "at_only"}, PASSWORD)
283
+ result = @sm.refresh(seal_data: sealed, cookie_password: PASSWORD)
284
+ assert_kind_of WorkOS::SessionManager::RefreshError, result
285
+ assert_equal WorkOS::SessionManager::INVALID_SESSION_COOKIE, result.reason
286
+ end
287
+
288
+ def test_refresh_does_not_send_session_param_to_api
289
+ rsa, pub = signing_key_pair
290
+ old_access = make_jwt({"sid" => "s", "exp" => Time.now.to_i - 60}, rsa)
291
+ sealed = @sm.seal_data({"access_token" => old_access, "refresh_token" => "rt_x", "user" => {"id" => "u"}}, PASSWORD)
292
+
293
+ new_access = make_jwt({"sid" => "s2", "exp" => Time.now.to_i + 300}, rsa)
294
+ api_response = {"access_token" => new_access, "refresh_token" => "rt_y", "user" => {"id" => "u"}}
295
+
296
+ stub = stub_request(:post, "https://api.workos.com/user_management/authenticate")
297
+ .with { |req| !req.body.include?("seal_session") }
298
+ .to_return(status: 200, body: api_response.to_json)
299
+ stub_request(:get, "https://api.workos.com/sso/jwks/client_001")
300
+ .to_return(status: 200, body: jwks_payload(pub).to_json)
301
+
302
+ session = @sm.load(seal_data: sealed, cookie_password: PASSWORD)
303
+ session.refresh
304
+
305
+ assert_requested(stub)
306
+ end
307
+
308
+ def test_refresh_returns_error_on_malformed_access_token_without_mutating_state
309
+ rsa, pub = signing_key_pair
310
+ old_access = make_jwt({"sid" => "session_old", "exp" => Time.now.to_i - 60}, rsa)
311
+ sealed = @sm.seal_data({"access_token" => old_access, "refresh_token" => "rt_old", "user" => {"id" => "u_1"}}, PASSWORD)
312
+
313
+ api_response = {
314
+ "access_token" => "not-a-valid-jwt",
315
+ "refresh_token" => "rt_new",
316
+ "user" => {"id" => "u_1"}
317
+ }
318
+
319
+ stub_request(:post, "https://api.workos.com/user_management/authenticate")
320
+ .to_return(status: 200, body: api_response.to_json)
321
+ stub_request(:get, "https://api.workos.com/sso/jwks/client_001")
322
+ .to_return(status: 200, body: jwks_payload(pub).to_json)
323
+
324
+ session = @sm.load(seal_data: sealed, cookie_password: PASSWORD)
325
+ result = session.refresh
326
+
327
+ assert_kind_of WorkOS::SessionManager::RefreshError, result
328
+ refute result.authenticated
329
+
330
+ # Session state should not have been mutated
331
+ assert_equal sealed, session.seal_data
332
+ end
333
+
209
334
  # --- Session constructor validation ---------------------------------------
210
335
 
211
336
  def test_session_load_requires_cookie_password
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: 7.1.0
4
+ version: 7.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - WorkOS
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-27 00:00:00.000000000 Z
11
+ date: 2026-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jwt