workos 5.28.0 → 5.30.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: f536a3eeb0b6d0fc877337104c31dc441ab6f32ccceef45ee7dad60bdba36e99
4
- data.tar.gz: c9a9bb5353eb0077d43f41da09ad95269034f4a81b4504db72ca13f3086f3a29
3
+ metadata.gz: e92012bf9ac81d3796ef280568d50188ec6cb01bd4dfdd9c053b61196a608063
4
+ data.tar.gz: 379504e11cbbf1e6d64670631b87c58b4a3bfac4b6fb724a68707423015d9641
5
5
  SHA512:
6
- metadata.gz: b689a75850258ff01f9929fdcfccbe8b76c816395d6266053fdabb2515a8c2fcdae43becd6cec2e5ed378cf6f2b6a0176bc38d92111c9bc12a4f44ec8b1b03e6
7
- data.tar.gz: da8fc3b665684df85724b12e8a9c7d86a95fdb26509b92c3e5a13923560c33c8106bc9a65e22d2abe2b7fcf444d3ba67121316b3b2cf75954ce2c51db256c71d
6
+ metadata.gz: 3b62dcaec41f4936b5027394952cd6d8f2050c80cd8676cbe69d1187d54a70d7e7b0f9673f9565a80b1de3662a64478321615521363eb61f7d8f72f8800b56fd
7
+ data.tar.gz: 594a6d8526ec2719e4c342433a95fe15b775809dc8921ac9c7e6dc25ecf11a2c97a59e095085c082b2ee83ff1490443fb868d80c9d35128849ae09ac6307a77d
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.30.1)
5
5
  encryptor (~> 3.0)
6
6
  jwt (~> 2.8)
7
7
 
@@ -8,7 +8,7 @@ module WorkOS
8
8
  include HashProvider
9
9
 
10
10
  attr_accessor :id, :idp_id, :email, :emails, :first_name, :last_name, :job_title, :username, :state,
11
- :groups, :role, :custom_attributes, :raw_attributes, :directory_id, :organization_id,
11
+ :groups, :role, :roles, :custom_attributes, :raw_attributes, :directory_id, :organization_id,
12
12
  :created_at, :updated_at
13
13
 
14
14
  # rubocop:disable Metrics/AbcSize
@@ -37,6 +37,7 @@ module WorkOS
37
37
  @state = hash[:state]
38
38
  @groups = hash[:groups]
39
39
  @role = hash[:role]
40
+ @roles = hash[:roles]
40
41
  @custom_attributes = hash[:custom_attributes]
41
42
  @raw_attributes = hash[:raw_attributes]
42
43
  @created_at = hash[:created_at]
@@ -47,6 +48,13 @@ module WorkOS
47
48
  # rubocop:enable Metrics/AbcSize
48
49
 
49
50
  def to_json(*)
51
+ base_attributes.
52
+ merge(authorization_attributes)
53
+ end
54
+
55
+ private
56
+
57
+ def base_attributes
50
58
  {
51
59
  id: id,
52
60
  directory_id: directory_id,
@@ -60,7 +68,6 @@ module WorkOS
60
68
  username: username,
61
69
  state: state,
62
70
  groups: groups,
63
- role: role,
64
71
  custom_attributes: custom_attributes,
65
72
  raw_attributes: raw_attributes,
66
73
  created_at: created_at,
@@ -68,6 +75,15 @@ module WorkOS
68
75
  }
69
76
  end
70
77
 
78
+ def authorization_attributes
79
+ {
80
+ role: role,
81
+ roles: roles,
82
+ }
83
+ end
84
+
85
+ public
86
+
71
87
  # @deprecated Will be removed in a future major version. Use {#email} instead.
72
88
  def primary_email
73
89
  primary_email = (emails || []).find { |email| email[:primary] }
@@ -9,7 +9,7 @@ module WorkOS
9
9
  class Profile
10
10
  include HashProvider
11
11
 
12
- attr_accessor :id, :email, :first_name, :last_name, :role, :groups, :organization_id,
12
+ attr_accessor :id, :email, :first_name, :last_name, :role, :roles, :groups, :organization_id,
13
13
  :connection_id, :connection_type, :idp_id, :custom_attributes, :raw_attributes
14
14
 
15
15
  # rubocop:disable Metrics/AbcSize
@@ -21,6 +21,7 @@ module WorkOS
21
21
  @first_name = hash[:first_name]
22
22
  @last_name = hash[:last_name]
23
23
  @role = hash[:role]
24
+ @roles = hash[:roles]
24
25
  @groups = hash[:groups]
25
26
  @organization_id = hash[:organization_id]
26
27
  @connection_id = hash[:connection_id]
@@ -42,6 +43,7 @@ module WorkOS
42
43
  first_name: first_name,
43
44
  last_name: last_name,
44
45
  role: role,
46
+ roles: roles,
45
47
  groups: groups,
46
48
  organization_id: organization_id,
47
49
  connection_id: connection_id,
@@ -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.30.1'
5
5
  end
@@ -37,13 +37,23 @@ describe WorkOS::DirectoryUser do
37
37
  it 'returns no role' do
38
38
  user = WorkOS::DirectoryUser.new('{"object":"directory_user","id":"directory_user_01FAZYNPC8M0HRYTKFP2GNX852","directory_id":"directory_01FAZYMST676QMTFN1DDJZZX87","idp_id":"6092c280a3f1e19ef6d8cef8","username":"bob.fakename@workos.com","emails":[{"primary":true,"value":"bob.fakename@workos.com"}, {"primary":false,"value":"bob.fakename@gmail.com"}],"first_name":"Bob","last_name":"Gingerich","job_title":"Developer Success Engineer","state":"active","raw_attributes":{},"custom_attributes":{},"groups":[],"created_at":"2022-05-13T17:45:31.732Z", "updated_at":"2022-07-13T17:45:42.618Z"}')
39
39
  expect(user.role).to eq(nil)
40
+ expect(user.roles).to eq(nil)
40
41
  end
41
42
  end
42
43
 
43
- context 'with a role' do
44
- it 'returns the role slug' do
45
- user = WorkOS::DirectoryUser.new('{"object":"directory_user","id":"directory_user_01FAZYNPC8M0HRYTKFP2GNX852","directory_id":"directory_01FAZYMST676QMTFN1DDJZZX87","idp_id":"6092c280a3f1e19ef6d8cef8","username":"bob.fakename@workos.com","emails":[{"primary":true,"value":"bob.fakename@workos.com"}, {"primary":false,"value":"bob.fakename@gmail.com"}],"first_name":"Bob","last_name":"Gingerich","job_title":"Developer Success Engineer","state":"active","raw_attributes":{},"custom_attributes":{},"groups":[],"role":{"slug":"member"},"created_at":"2022-05-13T17:45:31.732Z", "updated_at":"2022-07-13T17:45:42.618Z"}')
44
+ context 'with a single role' do
45
+ it 'returns the highest priority role slug and roles array' do
46
+ user = WorkOS::DirectoryUser.new('{"object":"directory_user","id":"directory_user_01FAZYNPC8M0HRYTKFP2GNX852","directory_id":"directory_01FAZYMST676QMTFN1DDJZZX87","idp_id":"6092c280a3f1e19ef6d8cef8","username":"bob.fakename@workos.com","emails":[{"primary":true,"value":"bob.fakename@workos.com"}, {"primary":false,"value":"bob.fakename@gmail.com"}],"first_name":"Bob","last_name":"Gingerich","job_title":"Developer Success Engineer","state":"active","raw_attributes":{},"custom_attributes":{},"groups":[],"role":{"slug":"member"},"roles":[{"slug":"member"}],"created_at":"2022-05-13T17:45:31.732Z", "updated_at":"2022-07-13T17:45:42.618Z"}')
46
47
  expect(user.role).to eq({ slug: 'member' })
48
+ expect(user.roles).to eq([{ slug: 'member' }])
49
+ end
50
+ end
51
+
52
+ context 'with multiple roles' do
53
+ it 'returns the highest priority role slug and roles array' do
54
+ user = WorkOS::DirectoryUser.new('{"object":"directory_user","id":"directory_user_01FAZYNPC8M0HRYTKFP2GNX852","directory_id":"directory_01FAZYMST676QMTFN1DDJZZX87","idp_id":"6092c280a3f1e19ef6d8cef8","username":"bob.fakename@workos.com","emails":[{"primary":true,"value":"bob.fakename@workos.com"}, {"primary":false,"value":"bob.fakename@gmail.com"}],"first_name":"Bob","last_name":"Gingerich","job_title":"Developer Success Engineer","state":"active","raw_attributes":{},"custom_attributes":{},"groups":[],"role":{"slug":"admin"},"roles":[{"slug":"member"}, {"slug":"admin"}],"created_at":"2022-05-13T17:45:31.732Z", "updated_at":"2022-07-13T17:45:42.618Z"}')
55
+ expect(user.role).to eq({ slug: 'admin' })
56
+ expect(user.roles).to eq([{ slug: 'member' }, { slug: 'admin' }])
47
57
  end
48
58
  end
49
59
  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,
@@ -305,6 +305,9 @@ describe WorkOS::SSO do
305
305
  role: {
306
306
  slug: 'member',
307
307
  },
308
+ roles: [{
309
+ slug: 'member',
310
+ }],
308
311
  groups: nil,
309
312
  organization_id: 'org_01FG53X8636WSNW2WEKB2C31ZB',
310
313
  custom_attributes: {},
@@ -380,6 +383,9 @@ describe WorkOS::SSO do
380
383
  role: {
381
384
  slug: 'admin',
382
385
  },
386
+ roles: [{
387
+ slug: 'admin',
388
+ }],
383
389
  groups: %w[Admins Developers],
384
390
  organization_id: 'org_01FG53X8636WSNW2WEKB2C31ZB',
385
391
  custom_attributes: {
@@ -74,7 +74,7 @@ http_interactions:
74
74
  - 72abbbf2b93e8ca5-EWR
75
75
  body:
76
76
  encoding: ASCII-8BIT
77
- string: '{"object":"directory_user","id":"directory_user_01FAZYNPC8M0HRYTKFP2GNX852","directory_id":"directory_01FAZYMST676QMTFN1DDJZZX87","organization_id":"org_01FAZWCWR03DVWA83NCJYKKD54","idp_id":"6092c280a3f1e19ef6d8cef8","username":"bob.fakename@workos.com","emails":[{"primary":true,"value":"bob.fakename@workos.com"}],"first_name":"Bob","last_name":"Fakename","job_title":"Developer Success Engineer","state":"active","raw_attributes":{"id":"6092c280a3f1e19ef6d8cef8","name":"Bob
77
+ string: '{"object":"directory_user","id":"directory_user_01FAZYNPC8M0HRYTKFP2GNX852","directory_id":"directory_01FAZYMST676QMTFN1DDJZZX87","organization_id":"org_01FAZWCWR03DVWA83NCJYKKD54","idp_id":"6092c280a3f1e19ef6d8cef8","username":"bob.fakename@workos.com","emails":[{"primary":true,"value":"bob.fakename@workos.com"}],"first_name":"Bob","last_name":"Fakename","job_title":"Developer Success Engineer","state":"active","role":{"slug":"member"},"roles":[{"slug":"member"}],"raw_attributes":{"id":"6092c280a3f1e19ef6d8cef8","name":"Bob
78
78
  Bob Fakename","teams":["5f696c8e9a63a60e965aaca8"],"spokeId":null,"lastName":"Fakename","created_at":"2021-05-05T16:06:24+0000","firstName":"Bob","updated_at":"2021-11-11T05:08:14+0000","workEmail":"bob.fakename@workos.com","department":"Infra","departmentId":"5f27ada9a5e9bc0001a0ae4a"},"custom_attributes":{},"created_at":"2021-07-19T17:57:57.268Z","updated_at":"2022-01-24T11:23:01.614Z","groups":[{"object":"directory_group","id":"directory_group_01FAZYNNQ4HQ6EBF29MPTH7VKB","idp_id":"5f696c8e9a63a60e965aaca8","directory_id":"directory_01FAZYMST676QMTFN1DDJZZX87","organization_id":"org_01FAZWCWR03DVWA83NCJYKKD54","name":"Team
79
79
  - Platform","created_at":"2021-07-19T17:57:56.580Z","updated_at":"2022-01-24T11:23:01.333Z","raw_attributes":{"id":"5f696c8e9a63a60e965aaca8","name":"Platform","parent":null}},{"object":"directory_group","id":"directory_group_01FAZYNN1NZWMBRAXXDSTB5NFH","idp_id":"5f27ada9a5e9bc0001a0ae4a","directory_id":"directory_01FAZYMST676QMTFN1DDJZZX87","organization_id":"org_01FAZWCWR03DVWA83NCJYKKD54","name":"Department
80
80
  - Infra","role":{"slug":"member"},"created_at":"2021-07-19T17:57:55.893Z","updated_at":"2022-01-24T11:23:01.333Z","raw_attributes":{"id":"5f27ada9a5e9bc0001a0ae4a","name":"Infra","parent":"5f27ada9a5e9bc0001a0ae48"}}]}'
@@ -67,7 +67,7 @@ http_interactions:
67
67
  body:
68
68
  encoding: UTF-8
69
69
  string:
70
- '{"object":"profile","id":"prof_01EEJTY9SZ1R350RB7B73SNBKF","organization_id":"org_01FG53X8636WSNW2WEKB2C31ZB","connection_id":"conn_01E83FVYZHY7DM4S9503JHV0R5","connection_type":"GoogleOAuth","idp_id":"116485463307139932699","email":"bob.loblaw@workos.com","first_name":"Bob","last_name":"Loblaw","role":{"slug":"member"},"custom_attributes":{},"raw_attributes":{"hd":"workos.com","id":"116485463307139932699","name":"Bob
70
+ '{"object":"profile","id":"prof_01EEJTY9SZ1R350RB7B73SNBKF","organization_id":"org_01FG53X8636WSNW2WEKB2C31ZB","connection_id":"conn_01E83FVYZHY7DM4S9503JHV0R5","connection_type":"GoogleOAuth","idp_id":"116485463307139932699","email":"bob.loblaw@workos.com","first_name":"Bob","last_name":"Loblaw","role":{"slug":"member"},"roles":[{"slug":"member"}],"custom_attributes":{},"raw_attributes":{"hd":"workos.com","id":"116485463307139932699","name":"Bob
71
71
  Loblaw","email":"bob.loblaw@workos.com","locale":"en","picture":"https://lh3.googleusercontent.com/a-/AOh14GyO2hLlgZvteDQ3Ldi3_-RteZLya0hWH7247Cam=s96-c","given_name":"Bob","family_name":"Loblaw","verified_email":true}}'
72
72
  http_version:
73
73
  recorded_at: Tue, 18 May 2021 22:55:21 GMT
@@ -1 +1 @@
1
- {"profile":{"object":"profile","id":"prof_01DRA1XNSJDZ19A31F183ECQW5","email":"demo@workos-okta.com","first_name":"WorkOS","organization_id":"org_01FG53X8636WSNW2WEKB2C31ZB","connection_id":"conn_01EMH8WAK20T42N2NBMNBCYHAG","connection_type":"OktaSAML","last_name":"Demo","role":{"slug": "admin"},"groups":["Admins","Developers"],"idp_id":"00u1klkowm8EGah2H357","custom_attributes":{"license": "professional"},"raw_attributes":{"id":"prof_01DRA1XNSJDZ19A31F183ECQW5","email":"demo@workos-okta.com","first_name":"WorkOS","last_name":"Demo","groups":["Admins","Developers"],"idp_id":"00u1klkowm8EGah2H357","license": "professional"}},"access_token":"01DVX6QBS3EG6FHY2ESAA5Q65X"}
1
+ {"profile":{"object":"profile","id":"prof_01DRA1XNSJDZ19A31F183ECQW5","email":"demo@workos-okta.com","first_name":"WorkOS","organization_id":"org_01FG53X8636WSNW2WEKB2C31ZB","connection_id":"conn_01EMH8WAK20T42N2NBMNBCYHAG","connection_type":"OktaSAML","last_name":"Demo","role":{"slug": "admin"},"roles":[{"slug": "admin"}],"groups":["Admins","Developers"],"idp_id":"00u1klkowm8EGah2H357","custom_attributes":{"license": "professional"},"raw_attributes":{"id":"prof_01DRA1XNSJDZ19A31F183ECQW5","email":"demo@workos-okta.com","first_name":"WorkOS","last_name":"Demo","groups":["Admins","Developers"],"idp_id":"00u1klkowm8EGah2H357","license": "professional"}},"access_token":"01DVX6QBS3EG6FHY2ESAA5Q65X"}
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.30.1
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-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: encryptor