workos 7.1.2 → 8.0.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.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +49 -0
  3. data/.github/workflows/release-please.yml +2 -2
  4. data/.gitignore +2 -0
  5. data/.last-synced-sha +1 -1
  6. data/.oagen-manifest.json +61 -40
  7. data/.release-please-manifest.json +1 -1
  8. data/.yardopts +6 -0
  9. data/CHANGELOG.md +36 -0
  10. data/Gemfile +6 -0
  11. data/Gemfile.lock +33 -2
  12. data/README.md +19 -0
  13. data/docs/V7_MIGRATION_GUIDE.md +21 -0
  14. data/lib/workos/actions.rb +1 -1
  15. data/lib/workos/api_keys/api_key.rb +1 -1
  16. data/lib/workos/api_keys/api_key_created_data.rb +1 -1
  17. data/lib/workos/api_keys/organization_api_key.rb +43 -0
  18. data/lib/workos/{types/events_order.rb → api_keys/organization_api_key_owner.rb} +1 -3
  19. data/lib/workos/api_keys/organization_api_key_with_value.rb +46 -0
  20. data/lib/workos/{types/audit_logs_order.rb → api_keys/organization_api_key_with_value_owner.rb} +1 -3
  21. data/lib/workos/api_keys.rb +46 -46
  22. data/lib/workos/audit_logs.rb +4 -4
  23. data/lib/workos/authorization/user_organization_membership_base_list_data.rb +5 -2
  24. data/lib/workos/authorization/{role_assignment.rb → user_role_assignment.rb} +5 -2
  25. data/lib/workos/authorization/{role_assignment_resource.rb → user_role_assignment_resource.rb} +1 -1
  26. data/lib/workos/authorization.rb +122 -22
  27. data/lib/workos/base_client.rb +71 -5
  28. data/lib/workos/client.rb +4 -4
  29. data/lib/workos/connect.rb +2 -2
  30. data/lib/workos/directory_sync/directory_user.rb +3 -0
  31. data/lib/workos/directory_sync/directory_user_with_groups.rb +4 -1
  32. data/lib/workos/directory_sync/dsync_user_updated_data.rb +3 -0
  33. data/lib/workos/directory_sync.rb +6 -6
  34. data/lib/workos/encryptors/aes_gcm.rb +19 -5
  35. data/lib/workos/events.rb +2 -2
  36. data/lib/workos/feature_flags.rb +6 -6
  37. data/lib/workos/groups.rb +4 -4
  38. data/lib/workos/multi_factor_auth.rb +2 -2
  39. data/lib/workos/organizations.rb +2 -2
  40. data/lib/workos/session.rb +28 -7
  41. data/lib/workos/session_manager.rb +24 -1
  42. data/lib/workos/sso/profile.rb +3 -0
  43. data/lib/workos/sso.rb +2 -2
  44. data/lib/workos/types/event_context_actor_source.rb +2 -1
  45. data/lib/workos/types/{applications_order.rb → pagination_order.rb} +1 -1
  46. data/lib/workos/types/{vault_byok_key_verification_completed_data_key_provider.rb → vault_byok_key_provider.rb} +1 -1
  47. data/lib/workos/user_management/create_user_api_key.rb +25 -0
  48. data/lib/workos/user_management/organization_membership.rb +5 -2
  49. data/lib/workos/user_management/user_api_key.rb +43 -0
  50. data/lib/workos/user_management/user_api_key_created_data_owner.rb +25 -0
  51. data/lib/workos/{api_keys/api_key_with_value_owner.rb → user_management/user_api_key_owner.rb} +1 -1
  52. data/lib/workos/{types/webhooks_order.rb → user_management/user_api_key_revoked_data_owner.rb} +1 -3
  53. data/lib/workos/{api_keys/api_key_with_value.rb → user_management/user_api_key_with_value.rb} +2 -2
  54. data/lib/workos/{types/groups_order.rb → user_management/user_api_key_with_value_owner.rb} +1 -3
  55. data/lib/workos/user_management/user_organization_membership.rb +5 -2
  56. data/lib/workos/user_management.rb +114 -10
  57. data/lib/workos/user_management_organization_membership_groups.rb +2 -2
  58. data/lib/workos/vault/vault_byok_key_deleted.rb +34 -0
  59. data/lib/workos/vault/vault_byok_key_deleted_data.rb +22 -0
  60. data/lib/workos/version.rb +1 -1
  61. data/lib/workos/webhooks.rb +3 -3
  62. data/rbi/workos/api_key.rbi +2 -2
  63. data/rbi/workos/api_key_created_data.rbi +2 -2
  64. data/rbi/workos/api_key_revoked_data.rbi +2 -2
  65. data/rbi/workos/api_keys.rbi +17 -17
  66. data/rbi/workos/authorization.rbi +27 -1
  67. data/rbi/workos/client.rbi +3 -3
  68. data/rbi/workos/create_user_api_key.rbi +36 -0
  69. data/rbi/workos/directory_user.rbi +6 -0
  70. data/rbi/workos/directory_user_with_groups.rbi +6 -0
  71. data/rbi/workos/dsync_user_updated_data.rbi +6 -0
  72. data/rbi/workos/organization_api_key.rbi +72 -0
  73. data/rbi/workos/{api_key_with_value_owner.rbi → organization_api_key_owner.rbi} +1 -1
  74. data/rbi/workos/organization_api_key_with_value.rbi +78 -0
  75. data/rbi/workos/organization_api_key_with_value_owner.rbi +30 -0
  76. data/rbi/workos/organization_membership.rbi +6 -0
  77. data/rbi/workos/profile.rbi +6 -0
  78. data/rbi/workos/user_api_key.rbi +72 -0
  79. data/rbi/workos/user_api_key_created_data_owner.rbi +36 -0
  80. data/rbi/workos/user_api_key_owner.rbi +36 -0
  81. data/rbi/workos/user_api_key_revoked_data_owner.rbi +36 -0
  82. data/rbi/workos/{api_key_with_value.rbi → user_api_key_with_value.rbi} +3 -3
  83. data/rbi/workos/user_api_key_with_value_owner.rbi +36 -0
  84. data/rbi/workos/user_management.rbi +31 -0
  85. data/rbi/workos/user_organization_membership.rbi +6 -0
  86. data/rbi/workos/user_organization_membership_base_list_data.rbi +6 -0
  87. data/rbi/workos/{role_assignment.rbi → user_role_assignment.rbi} +9 -3
  88. data/rbi/workos/{role_assignment_resource.rbi → user_role_assignment_resource.rbi} +1 -1
  89. data/rbi/workos/vault_byok_key_deleted.rbi +54 -0
  90. data/rbi/workos/vault_byok_key_deleted_data.rbi +30 -0
  91. data/script/docs +16 -0
  92. data/script/docs-serve +12 -0
  93. data/script/llms-txt +37 -0
  94. data/test/workos/test_actions.rb +9 -0
  95. data/test/workos/test_api_keys.rb +17 -17
  96. data/test/workos/test_authorization.rb +16 -0
  97. data/test/workos/test_base_client.rb +44 -0
  98. data/test/workos/test_encryptors_aes_gcm.rb +16 -1
  99. data/test/workos/test_model_round_trip.rb +278 -83
  100. data/test/workos/test_session.rb +43 -4
  101. data/test/workos/test_user_management.rb +25 -1
  102. data/test/workos/test_webhook_verify.rb +11 -0
  103. metadata +39 -33
  104. data/lib/workos/types/authorization_order.rb +0 -9
  105. data/lib/workos/types/connections_order.rb +0 -9
  106. data/lib/workos/types/directories_order.rb +0 -9
  107. data/lib/workos/types/directory_groups_order.rb +0 -9
  108. data/lib/workos/types/directory_users_order.rb +0 -9
  109. data/lib/workos/types/feature_flags_order.rb +0 -9
  110. data/lib/workos/types/organizations_api_keys_order.rb +0 -9
  111. data/lib/workos/types/organizations_feature_flags_order.rb +0 -9
  112. data/lib/workos/types/organizations_order.rb +0 -9
  113. data/lib/workos/types/permissions_order.rb +0 -9
  114. data/lib/workos/types/user_management_invitations_order.rb +0 -9
  115. data/lib/workos/types/user_management_multi_factor_authentication_order.rb +0 -9
  116. data/lib/workos/types/user_management_organization_membership_groups_order.rb +0 -9
  117. data/lib/workos/types/user_management_organization_membership_order.rb +0 -9
  118. data/lib/workos/types/user_management_users_authorized_applications_order.rb +0 -9
  119. data/lib/workos/types/user_management_users_feature_flags_order.rb +0 -9
  120. data/lib/workos/types/user_management_users_order.rb +0 -9
@@ -134,7 +134,8 @@ module WorkOS
134
134
  attempt = 0
135
135
 
136
136
  loop do
137
- log(:debug, "request start", method: request.method, path: request.path, attempt: attempt + 1)
137
+ loggable_path = redact_path(request.path)
138
+ log(:debug, "request start", method: request.method, path: loggable_path, attempt: attempt + 1)
138
139
  http = connection_for(base, timeout)
139
140
  response = http.request(request)
140
141
  return response if response.is_a?(Net::HTTPSuccess)
@@ -142,11 +143,11 @@ module WorkOS
142
143
  if attempt < retries && retryable?(response)
143
144
  attempt += 1
144
145
  inject_retry_idempotency_key(request)
145
- log(:info, "request retry", method: request.method, path: request.path, attempt: attempt + 1, status: response.code.to_i)
146
+ log(:info, "request retry", method: request.method, path: loggable_path, attempt: attempt + 1, status: response.code.to_i)
146
147
  sleep(retry_delay(response, attempt))
147
148
  next
148
149
  end
149
- log(:warn, "request error", method: request.method, path: request.path, status: response.code.to_i, request_id: response["x-request-id"] || response["X-Request-Id"])
150
+ log(:warn, "request error", method: request.method, path: loggable_path, status: response.code.to_i, request_id: response["x-request-id"] || response["X-Request-Id"])
150
151
  handle_error_response(response)
151
152
  rescue Net::OpenTimeout, Net::ReadTimeout,
152
153
  Errno::ECONNRESET, Errno::ECONNREFUSED,
@@ -155,11 +156,11 @@ module WorkOS
155
156
  if attempt < retries
156
157
  attempt += 1
157
158
  inject_retry_idempotency_key(request)
158
- log(:info, "request retry", method: request.method, path: request.path, attempt: attempt + 1, error: e.class.name)
159
+ log(:info, "request retry", method: request.method, path: loggable_path, attempt: attempt + 1, error: e.class.name)
159
160
  sleep(retry_delay(nil, attempt))
160
161
  next
161
162
  end
162
- log(:warn, "connection error", method: request.method, path: request.path, error: e.class.name, message: e.message)
163
+ log(:warn, "connection error", method: request.method, path: loggable_path, error: e.class.name, message: e.message)
163
164
  raise WorkOS::APIConnectionError.new(message: e.message)
164
165
  end
165
166
  end
@@ -179,6 +180,71 @@ module WorkOS
179
180
 
180
181
  private
181
182
 
183
+ # Redact path segments that carry bearer-equivalent tokens (e.g.
184
+ # `/user_management/invitations/by_token/<token>`,
185
+ # `/user_management/magic_auth/<token>`, password-reset / email-
186
+ # verification token paths) before the path is written to a logger.
187
+ # The WorkOS API exposes a small number of "by_token" endpoints whose
188
+ # path segments are themselves authentication material; redacting them
189
+ # here means the SDK never emits the token in its own log/retry/error
190
+ # messages even when the host application configures verbose logging.
191
+ REDACTED_TOKEN_PREFIXES = %w[
192
+ /user_management/invitations/by_token
193
+ /user_management/magic_auth
194
+ /user_management/password_reset
195
+ /user_management/email_verification
196
+ ].freeze
197
+ private_constant :REDACTED_TOKEN_PREFIXES
198
+
199
+ # Query-string keys whose values are bearer-equivalent or otherwise
200
+ # sensitive and should never appear in SDK log lines. Defense-in-depth
201
+ # for any path that flows through execute_request with a sensitive
202
+ # query parameter (most WorkOS-issued tokens are path segments or POST
203
+ # bodies, but a few flows — e.g. authorize/logout redirects — surface
204
+ # them in the query string).
205
+ REDACTED_QUERY_KEYS = %w[
206
+ token
207
+ code
208
+ code_challenge
209
+ code_verifier
210
+ session_id
211
+ refresh_token
212
+ access_token
213
+ ].freeze
214
+ private_constant :REDACTED_QUERY_KEYS
215
+
216
+ def redact_path(path)
217
+ return path if path.nil? || path.empty?
218
+
219
+ # Strip query string for the prefix match; reattach (scrubbed) after.
220
+ path_only, query = path.split("?", 2)
221
+ REDACTED_TOKEN_PREFIXES.each do |prefix|
222
+ next unless path_only.start_with?("#{prefix}/")
223
+
224
+ # Replace every segment after the matched prefix with "[REDACTED]".
225
+ remainder = path_only[(prefix.length + 1)..]
226
+ next if remainder.nil? || remainder.empty?
227
+
228
+ redacted = remainder.split("/").map { "[REDACTED]" }.join("/")
229
+ path_only = "#{prefix}/#{redacted}"
230
+ break
231
+ end
232
+ query ? "#{path_only}?#{redact_query(query)}" : path_only
233
+ end
234
+
235
+ def redact_query(query)
236
+ return query if query.nil? || query.empty?
237
+
238
+ query.split("&").map { |pair|
239
+ key, value = pair.split("=", 2)
240
+ if value && !value.empty? && REDACTED_QUERY_KEYS.include?(key)
241
+ "#{key}=[REDACTED]"
242
+ else
243
+ pair
244
+ end
245
+ }.join("&")
246
+ end
247
+
182
248
  def append_query(path, params)
183
249
  return path unless params.is_a?(Hash) && !params.empty?
184
250
 
data/lib/workos/client.rb CHANGED
@@ -4,10 +4,6 @@
4
4
 
5
5
  module WorkOS
6
6
  class Client < BaseClient
7
- def api_keys
8
- @api_keys ||= WorkOS::ApiKeys.new(self)
9
- end
10
-
11
7
  def multi_factor_auth
12
8
  @multi_factor_auth ||= WorkOS::MultiFactorAuth.new(self)
13
9
  end
@@ -48,6 +44,10 @@ module WorkOS
48
44
  @organizations ||= WorkOS::Organizations.new(self)
49
45
  end
50
46
 
47
+ def api_keys
48
+ @api_keys ||= WorkOS::ApiKeys.new(self)
49
+ end
50
+
51
51
  def groups
52
52
  @groups ||= WorkOS::Groups.new(self)
53
53
  end
@@ -43,14 +43,14 @@ module WorkOS
43
43
  # @param before [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`.
44
44
  # @param after [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`.
45
45
  # @param limit [Integer, nil] Upper limit on the number of objects to return, between `1` and `100`.
46
- # @param order [WorkOS::Types::ApplicationsOrder, nil] Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to descending.
46
+ # @param order [WorkOS::Types::PaginationOrder, nil] Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to descending.
47
47
  # @param organization_id [String, nil] Filter Connect Applications by organization ID.
48
48
  # @param request_options [Hash] (see WorkOS::Types::RequestOptions)
49
49
  # @return [WorkOS::Types::ListStruct<WorkOS::ConnectApplication>]
50
50
  def list_applications(
51
51
  before: nil,
52
52
  after: nil,
53
- limit: nil,
53
+ limit: 10,
54
54
  order: "desc",
55
55
  organization_id: nil,
56
56
  request_options: {}
@@ -13,6 +13,7 @@ module WorkOS
13
13
  email: :email,
14
14
  first_name: :first_name,
15
15
  last_name: :last_name,
16
+ name: :name,
16
17
  emails: :emails,
17
18
  job_title: :job_title,
18
19
  username: :username,
@@ -43,6 +44,7 @@ module WorkOS
43
44
  :email,
44
45
  :first_name,
45
46
  :last_name,
47
+ :name,
46
48
  :state,
47
49
  :custom_attributes,
48
50
  :role,
@@ -88,6 +90,7 @@ module WorkOS
88
90
  @email = hash[:email]
89
91
  @first_name = hash[:first_name]
90
92
  @last_name = hash[:last_name]
93
+ @name = hash[:name]
91
94
  @emails = (hash[:emails] || []).map { |item| item ? WorkOS::DirectoryUserEmail.new(item) : nil }
92
95
  @job_title = hash[:job_title]
93
96
  @username = hash[:username]
@@ -13,6 +13,7 @@ module WorkOS
13
13
  email: :email,
14
14
  first_name: :first_name,
15
15
  last_name: :last_name,
16
+ name: :name,
16
17
  emails: :emails,
17
18
  job_title: :job_title,
18
19
  username: :username,
@@ -35,7 +36,7 @@ module WorkOS
35
36
  # @!attribute raw_attributes
36
37
  # @deprecated The raw attributes received from the directory provider.
37
38
  # @!attribute groups
38
- # @deprecated The directory groups the user belongs to. Use the List Directory Groups endpoint with a user filter instead.
39
+ # @deprecated The directory groups the user belongs to. Deprecated: starting May 1, 2026, this field returns an empty array by default for newly created teams. Existing teams currently depending on this field should migrate to the new access pattern for better throughput performance — the field is unbounded by user, so users with many group memberships produce large, slow response payloads. Use the List Directory Groups endpoint with a `user` filter to fetch a user's group memberships.
39
40
 
40
41
  attr_accessor \
41
42
  :object,
@@ -46,6 +47,7 @@ module WorkOS
46
47
  :email,
47
48
  :first_name,
48
49
  :last_name,
50
+ :name,
49
51
  :state,
50
52
  :custom_attributes,
51
53
  :role,
@@ -98,6 +100,7 @@ module WorkOS
98
100
  @email = hash[:email]
99
101
  @first_name = hash[:first_name]
100
102
  @last_name = hash[:last_name]
103
+ @name = hash[:name]
101
104
  @emails = (hash[:emails] || []).map { |item| item ? WorkOS::DirectoryUserWithGroupsEmail.new(item) : nil }
102
105
  @job_title = hash[:job_title]
103
106
  @username = hash[:username]
@@ -13,6 +13,7 @@ module WorkOS
13
13
  email: :email,
14
14
  first_name: :first_name,
15
15
  last_name: :last_name,
16
+ name: :name,
16
17
  emails: :emails,
17
18
  job_title: :job_title,
18
19
  username: :username,
@@ -44,6 +45,7 @@ module WorkOS
44
45
  :email,
45
46
  :first_name,
46
47
  :last_name,
48
+ :name,
47
49
  :state,
48
50
  :custom_attributes,
49
51
  :role,
@@ -90,6 +92,7 @@ module WorkOS
90
92
  @email = hash[:email]
91
93
  @first_name = hash[:first_name]
92
94
  @last_name = hash[:last_name]
95
+ @name = hash[:name]
93
96
  @emails = (hash[:emails] || []).map { |item| item ? WorkOS::DsyncUserUpdatedDataEmail.new(item) : nil }
94
97
  @job_title = hash[:job_title]
95
98
  @username = hash[:username]
@@ -14,7 +14,7 @@ module WorkOS
14
14
  # @param before [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list.
15
15
  # @param after [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list.
16
16
  # @param limit [Integer, nil] Upper limit on the number of objects to return, between `1` and `100`.
17
- # @param order [WorkOS::Types::DirectoriesOrder, nil] Order the results by the creation time.
17
+ # @param order [WorkOS::Types::PaginationOrder, nil] Order the results by the creation time.
18
18
  # @param organization_id [String, nil] Filter Directories by their associated organization.
19
19
  # @param search [String, nil] Searchable text to match against Directory names.
20
20
  # @param domain [String, nil] (deprecated) Filter Directories by their associated domain.
@@ -23,7 +23,7 @@ module WorkOS
23
23
  def list_directories(
24
24
  before: nil,
25
25
  after: nil,
26
- limit: nil,
26
+ limit: 10,
27
27
  order: "desc",
28
28
  organization_id: nil,
29
29
  search: nil,
@@ -106,7 +106,7 @@ module WorkOS
106
106
  # @param before [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`.
107
107
  # @param after [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`.
108
108
  # @param limit [Integer, nil] Upper limit on the number of objects to return, between `1` and `100`.
109
- # @param order [WorkOS::Types::DirectoryGroupsOrder, nil] Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to descending.
109
+ # @param order [WorkOS::Types::PaginationOrder, nil] Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to descending.
110
110
  # @param directory [String, nil] Unique identifier of the WorkOS Directory. This value can be obtained from the WorkOS dashboard or from the WorkOS API.
111
111
  # @param user [String, nil] Unique identifier of the WorkOS Directory User. This value can be obtained from the WorkOS API.
112
112
  # @param request_options [Hash] (see WorkOS::Types::RequestOptions)
@@ -114,7 +114,7 @@ module WorkOS
114
114
  def list_groups(
115
115
  before: nil,
116
116
  after: nil,
117
- limit: nil,
117
+ limit: 10,
118
118
  order: "desc",
119
119
  directory: nil,
120
120
  user: nil,
@@ -177,7 +177,7 @@ module WorkOS
177
177
  # @param before [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`.
178
178
  # @param after [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`.
179
179
  # @param limit [Integer, nil] Upper limit on the number of objects to return, between `1` and `100`.
180
- # @param order [WorkOS::Types::DirectoryUsersOrder, nil] Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to descending.
180
+ # @param order [WorkOS::Types::PaginationOrder, nil] Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to descending.
181
181
  # @param directory [String, nil] Unique identifier of the WorkOS Directory. This value can be obtained from the WorkOS dashboard or from the WorkOS API.
182
182
  # @param group [String, nil] Unique identifier of the WorkOS Directory Group. This value can be obtained from the WorkOS API.
183
183
  # @param request_options [Hash] (see WorkOS::Types::RequestOptions)
@@ -185,7 +185,7 @@ module WorkOS
185
185
  def list_users(
186
186
  before: nil,
187
187
  after: nil,
188
- limit: nil,
188
+ limit: 10,
189
189
  order: "desc",
190
190
  directory: nil,
191
191
  group: nil,
@@ -14,8 +14,14 @@ module WorkOS
14
14
  module Encryptors
15
15
  class AesGcm
16
16
  SEAL_VERSION = 0x01
17
+ # Minimum cookie_password byte length. AES-256-GCM derives a 32-byte
18
+ # key from the password via SHA-256; a passphrase shorter than the
19
+ # output it derives to provides less than the full keyspace and makes
20
+ # offline brute-force feasible. See README + V7_MIGRATION_GUIDE.md.
21
+ MIN_KEY_BYTES = 32
17
22
 
18
23
  def seal(data, key)
24
+ validate_key!(key)
19
25
  json = data.is_a?(String) ? data : JSON.generate(data)
20
26
  cipher = OpenSSL::Cipher.new("aes-256-gcm").encrypt
21
27
  cipher.key = derive_key(key)
@@ -26,13 +32,16 @@ module WorkOS
26
32
  end
27
33
 
28
34
  def unseal(sealed, key)
35
+ validate_key!(key)
29
36
  raw = Base64.decode64(sealed.to_s)
30
- decode_v7(raw, key)
31
- rescue ArgumentError, OpenSSL::Cipher::CipherError => original_error
32
37
  begin
33
- decode_old(raw, key)
34
- rescue ArgumentError, OpenSSL::Cipher::CipherError
35
- raise original_error
38
+ decode_v7(raw, key)
39
+ rescue ArgumentError, OpenSSL::Cipher::CipherError => original_error
40
+ begin
41
+ decode_old(raw, key)
42
+ rescue ArgumentError, OpenSSL::Cipher::CipherError
43
+ raise original_error
44
+ end
36
45
  end
37
46
  end
38
47
 
@@ -83,6 +92,11 @@ module WorkOS
83
92
  def derive_key(passphrase)
84
93
  Digest::SHA256.digest(passphrase.to_s)
85
94
  end
95
+
96
+ def validate_key!(key)
97
+ raise ArgumentError, "cookie_password is required" if key.nil? || key.to_s.empty?
98
+ raise ArgumentError, "cookie_password must be at least #{MIN_KEY_BYTES} bytes" if key.to_s.bytesize < MIN_KEY_BYTES
99
+ end
86
100
  end
87
101
  end
88
102
  end
data/lib/workos/events.rb CHANGED
@@ -14,7 +14,7 @@ module WorkOS
14
14
  # @param before [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`.
15
15
  # @param after [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`.
16
16
  # @param limit [Integer, nil] Upper limit on the number of objects to return, between `1` and `100`.
17
- # @param order [WorkOS::Types::EventsOrder, nil] Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to descending.
17
+ # @param order [WorkOS::Types::PaginationOrder, nil] Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to descending.
18
18
  # @param events [Array<String>, nil] Filter events by one or more event types (e.g. `dsync.user.created`).
19
19
  # @param range_start [String, nil] ISO-8601 date string to filter events created after this date.
20
20
  # @param range_end [String, nil] ISO-8601 date string to filter events created before this date.
@@ -24,7 +24,7 @@ module WorkOS
24
24
  def list_events(
25
25
  before: nil,
26
26
  after: nil,
27
- limit: nil,
27
+ limit: 10,
28
28
  order: "desc",
29
29
  events: nil,
30
30
  range_start: nil,
@@ -14,13 +14,13 @@ module WorkOS
14
14
  # @param before [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list.
15
15
  # @param after [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list.
16
16
  # @param limit [Integer, nil] Upper limit on the number of objects to return, between `1` and `100`.
17
- # @param order [WorkOS::Types::FeatureFlagsOrder, nil] Order the results by the creation time.
17
+ # @param order [WorkOS::Types::PaginationOrder, nil] Order the results by the creation time.
18
18
  # @param request_options [Hash] (see WorkOS::Types::RequestOptions)
19
19
  # @return [WorkOS::Types::ListStruct<WorkOS::Flag>]
20
20
  def list_feature_flags(
21
21
  before: nil,
22
22
  after: nil,
23
- limit: nil,
23
+ limit: 10,
24
24
  order: "desc",
25
25
  request_options: {}
26
26
  )
@@ -154,14 +154,14 @@ module WorkOS
154
154
  # @param before [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list.
155
155
  # @param after [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list.
156
156
  # @param limit [Integer, nil] Upper limit on the number of objects to return, between `1` and `100`.
157
- # @param order [WorkOS::Types::OrganizationsFeatureFlagsOrder, nil] Order the results by the creation time.
157
+ # @param order [WorkOS::Types::PaginationOrder, nil] Order the results by the creation time.
158
158
  # @param request_options [Hash] (see WorkOS::Types::RequestOptions)
159
159
  # @return [WorkOS::Types::ListStruct<WorkOS::Flag>]
160
160
  def list_organization_feature_flags(
161
161
  organization_id:,
162
162
  before: nil,
163
163
  after: nil,
164
- limit: nil,
164
+ limit: 10,
165
165
  order: "desc",
166
166
  request_options: {}
167
167
  )
@@ -201,14 +201,14 @@ module WorkOS
201
201
  # @param before [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list.
202
202
  # @param after [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list.
203
203
  # @param limit [Integer, nil] Upper limit on the number of objects to return, between `1` and `100`.
204
- # @param order [WorkOS::Types::UserManagementUsersFeatureFlagsOrder, nil] Order the results by the creation time.
204
+ # @param order [WorkOS::Types::PaginationOrder, nil] Order the results by the creation time.
205
205
  # @param request_options [Hash] (see WorkOS::Types::RequestOptions)
206
206
  # @return [WorkOS::Types::ListStruct<WorkOS::Flag>]
207
207
  def list_user_feature_flags(
208
208
  user_id:,
209
209
  before: nil,
210
210
  after: nil,
211
- limit: nil,
211
+ limit: 10,
212
212
  order: "desc",
213
213
  request_options: {}
214
214
  )
data/lib/workos/groups.rb CHANGED
@@ -15,14 +15,14 @@ module WorkOS
15
15
  # @param before [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`.
16
16
  # @param after [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`.
17
17
  # @param limit [Integer, nil] Upper limit on the number of objects to return, between `1` and `100`.
18
- # @param order [WorkOS::Types::GroupsOrder, nil] Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to descending.
18
+ # @param order [WorkOS::Types::PaginationOrder, nil] Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to descending.
19
19
  # @param request_options [Hash] (see WorkOS::Types::RequestOptions)
20
20
  # @return [WorkOS::Types::ListStruct<WorkOS::Group>]
21
21
  def list_organization_groups(
22
22
  organization_id:,
23
23
  before: nil,
24
24
  after: nil,
25
- limit: nil,
25
+ limit: 10,
26
26
  order: "desc",
27
27
  request_options: {}
28
28
  )
@@ -161,7 +161,7 @@ module WorkOS
161
161
  # @param before [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`.
162
162
  # @param after [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`.
163
163
  # @param limit [Integer, nil] Upper limit on the number of objects to return, between `1` and `100`.
164
- # @param order [WorkOS::Types::GroupsOrder, nil] Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to descending.
164
+ # @param order [WorkOS::Types::PaginationOrder, nil] Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to descending.
165
165
  # @param request_options [Hash] (see WorkOS::Types::RequestOptions)
166
166
  # @return [WorkOS::Types::ListStruct<WorkOS::UserOrganizationMembershipBaseListData>]
167
167
  def list_group_organization_memberships(
@@ -169,7 +169,7 @@ module WorkOS
169
169
  group_id:,
170
170
  before: nil,
171
171
  after: nil,
172
- limit: nil,
172
+ limit: 10,
173
173
  order: "desc",
174
174
  request_options: {}
175
175
  )
@@ -136,14 +136,14 @@ module WorkOS
136
136
  # @param before [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list.
137
137
  # @param after [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list.
138
138
  # @param limit [Integer, nil] Upper limit on the number of objects to return, between `1` and `100`.
139
- # @param order [WorkOS::Types::UserManagementMultiFactorAuthenticationOrder, nil] Order the results by the creation time.
139
+ # @param order [WorkOS::Types::PaginationOrder, nil] Order the results by the creation time.
140
140
  # @param request_options [Hash] (see WorkOS::Types::RequestOptions)
141
141
  # @return [WorkOS::Types::ListStruct<WorkOS::AuthenticationFactor>]
142
142
  def list_user_auth_factors(
143
143
  userland_user_id:,
144
144
  before: nil,
145
145
  after: nil,
146
- limit: nil,
146
+ limit: 10,
147
147
  order: "desc",
148
148
  request_options: {}
149
149
  )
@@ -14,7 +14,7 @@ module WorkOS
14
14
  # @param before [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`.
15
15
  # @param after [String, nil] An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`.
16
16
  # @param limit [Integer, nil] Upper limit on the number of objects to return, between `1` and `100`.
17
- # @param order [WorkOS::Types::OrganizationsOrder, nil] Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to descending.
17
+ # @param order [WorkOS::Types::PaginationOrder, nil] Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to descending.
18
18
  # @param domains [Array<String>, nil] The domains of an Organization. Any Organization with a matching domain will be returned.
19
19
  # @param search [String, nil] Searchable text for an Organization. Matches against the organization name.
20
20
  # @param request_options [Hash] (see WorkOS::Types::RequestOptions)
@@ -22,7 +22,7 @@ module WorkOS
22
22
  def list_organizations(
23
23
  before: nil,
24
24
  after: nil,
25
- limit: nil,
25
+ limit: 10,
26
26
  order: "desc",
27
27
  domains: nil,
28
28
  search: nil,
@@ -21,8 +21,16 @@ module WorkOS
21
21
  # @example Build a logout URL
22
22
  # url = session.get_logout_url(return_to: "https://app.example.com")
23
23
  class Session
24
+ # Minimum cookie_password byte length. AES-256-GCM derives a 32-byte
25
+ # key from the password via SHA-256; a passphrase shorter than the
26
+ # output it derives to provides less than the full keyspace and makes
27
+ # offline brute-force feasible. Require callers to supply at least 32
28
+ # bytes of high-entropy secret. See README + V7_MIGRATION_GUIDE.md.
29
+ MIN_COOKIE_PASSWORD_BYTES = 32
30
+
24
31
  def initialize(manager, seal_data:, cookie_password:)
25
32
  raise ArgumentError, "cookie_password is required" if cookie_password.nil? || cookie_password.empty?
33
+ raise ArgumentError, "cookie_password must be at least #{MIN_COOKIE_PASSWORD_BYTES} bytes" if cookie_password.bytesize < MIN_COOKIE_PASSWORD_BYTES
26
34
  @manager = manager
27
35
  @client = manager.client
28
36
  @seal_data = seal_data
@@ -57,7 +65,7 @@ module WorkOS
57
65
  return SessionManager::AuthError.new(authenticated: false, reason: SessionManager::INVALID_JWT)
58
66
  end
59
67
 
60
- is_expired = decoded["exp"] && decoded["exp"] < Time.now.to_i
68
+ is_expired = decoded["exp"].nil? || decoded["exp"] < Time.now.to_i
61
69
 
62
70
  SessionManager::AuthSuccess.new(
63
71
  authenticated: !is_expired,
@@ -77,6 +85,11 @@ module WorkOS
77
85
 
78
86
  def refresh(organization_id: nil, cookie_password: nil)
79
87
  effective_password = cookie_password || @cookie_password
88
+ # Validate up front so a caller-supplied short password raises ArgumentError
89
+ # (matching Session#initialize) instead of being swallowed by the
90
+ # unseal_data rescue and surfacing as INVALID_SESSION_COOKIE.
91
+ raise ArgumentError, "cookie_password is required" if effective_password.nil? || effective_password.empty?
92
+ raise ArgumentError, "cookie_password must be at least #{MIN_COOKIE_PASSWORD_BYTES} bytes" if effective_password.bytesize < MIN_COOKIE_PASSWORD_BYTES
80
93
 
81
94
  session = begin
82
95
  @manager.unseal_data(@seal_data, effective_password)
@@ -105,17 +118,20 @@ module WorkOS
105
118
  impersonator: auth_response["impersonator"]
106
119
  )
107
120
 
108
- # Decode before mutating session state so a malformed access_token
109
- # doesn't leave the Session half-updated.
110
- decoded = @manager.decode_jwt(auth_response["access_token"])
111
-
121
+ # Persist the new seal/password BEFORE decoding the JWT, so a transient
122
+ # JWKS fetch error (or any decode failure on the freshly-minted token)
123
+ # leaves the Session with a usable sealed cookie that the caller can
124
+ # re-#authenticate against, rather than half-updated state.
112
125
  @seal_data = sealed
113
126
  @cookie_password = effective_password
127
+
128
+ decoded = @manager.decode_jwt(auth_response["access_token"])
129
+
114
130
  SessionManager::RefreshSuccess.new(
115
131
  authenticated: true,
116
132
  sealed_session: sealed,
117
133
  session_id: decoded["sid"],
118
- organization_id: decoded["org_id"],
134
+ organization_id: auth_response["organization_id"] || decoded["org_id"],
119
135
  role: decoded["role"],
120
136
  roles: decoded["roles"],
121
137
  permissions: decoded["permissions"],
@@ -127,7 +143,12 @@ module WorkOS
127
143
  rescue WorkOS::AuthenticationError, WorkOS::InvalidRequestError => e
128
144
  SessionManager::RefreshError.new(authenticated: false, reason: e.message)
129
145
  rescue JWT::DecodeError => e
130
- SessionManager::RefreshError.new(authenticated: false, reason: e.message)
146
+ # The refresh token was already rotated server-side before decode failed,
147
+ # so @seal_data holds the freshly-minted cookie. Surface it on the error
148
+ # struct so the caller can write the rotated cookie back to the browser
149
+ # and recover on a subsequent #authenticate, rather than re-sending the
150
+ # now-revoked refresh token.
151
+ SessionManager::RefreshError.new(authenticated: false, reason: e.message, sealed_session: @seal_data)
131
152
  end
132
153
 
133
154
  # Build the WorkOS session-logout URL for the currently authenticated session.
@@ -107,7 +107,7 @@ module WorkOS
107
107
  :roles, :permissions, :entitlements, :user, :impersonator, :feature_flags,
108
108
  keyword_init: true
109
109
  )
110
- RefreshError = Struct.new(:authenticated, :reason, keyword_init: true)
110
+ RefreshError = Struct.new(:authenticated, :reason, :sealed_session, keyword_init: true)
111
111
 
112
112
  # Failure reason constants
113
113
  NO_SESSION_COOKIE_PROVIDED = "no_session_cookie_provided"
@@ -150,12 +150,14 @@ module WorkOS
150
150
  # H06 — Raw seal: encrypt arbitrary data with a key string.
151
151
  # Delegates to the configured encryptor (default: AES-256-GCM).
152
152
  def seal_data(data, key)
153
+ validate_cookie_password!(key)
153
154
  @encryptor.seal(data, key)
154
155
  end
155
156
 
156
157
  # H06 — Raw unseal: returns parsed JSON (Hash) or raw string if not JSON.
157
158
  # Delegates to the configured encryptor (default: AES-256-GCM).
158
159
  def unseal_data(sealed, key)
160
+ validate_cookie_password!(key)
159
161
  @encryptor.unseal(sealed, key)
160
162
  end
161
163
 
@@ -164,11 +166,20 @@ module WorkOS
164
166
  payload = {"access_token" => access_token, "refresh_token" => refresh_token}
165
167
  payload["user"] = user if user
166
168
  payload["impersonator"] = impersonator if impersonator
169
+ # Delegates to seal_data, which calls validate_cookie_password!; no need
170
+ # to validate here too.
167
171
  seal_data(payload, cookie_password)
168
172
  end
169
173
 
170
174
  # Verify an access-token JWT against the WorkOS JWKS for this client.
171
175
  # Used by Session#authenticate; exposed publicly for advanced cases.
176
+ #
177
+ # NOTE on iss/aud/required_claims: this method intentionally does not
178
+ # enforce iss, aud, or required_claims. workos-node's `jose` call and
179
+ # workos-php's `isset($exp) && $exp < time()` accept exp-less tokens, and
180
+ # cross-SDK parity is required for the planned coordinated hardening of
181
+ # these claims. See commit 9ce069f for the rationale behind dropping the
182
+ # required_claims: ['exp'] tightening that was considered here.
172
183
  def decode_jwt(access_token, verify_expiration: true)
173
184
  jwks = fetch_jwks
174
185
  JWT.decode(
@@ -182,6 +193,18 @@ module WorkOS
182
193
  ).first
183
194
  end
184
195
 
196
+ private
197
+
198
+ # Validate a cookie_password is non-empty and at least the minimum
199
+ # byte length required by Session::MIN_COOKIE_PASSWORD_BYTES (32).
200
+ # Defense-in-depth — Session#initialize enforces the same invariant
201
+ # on the load path; this guards the inline #seal_data / #unseal_data
202
+ # entry points.
203
+ def validate_cookie_password!(key)
204
+ raise ArgumentError, "cookie_password is required" if key.nil? || key.empty?
205
+ raise ArgumentError, "cookie_password must be at least #{Session::MIN_COOKIE_PASSWORD_BYTES} bytes" if key.bytesize < Session::MIN_COOKIE_PASSWORD_BYTES
206
+ end
207
+
185
208
  # Cached JWKS fetch (5-minute TTL, thread-safe).
186
209
  def fetch_jwks(now: Time.now)
187
210
  @jwks_mutex.synchronize do
@@ -14,6 +14,7 @@ module WorkOS
14
14
  email: :email,
15
15
  first_name: :first_name,
16
16
  last_name: :last_name,
17
+ name: :name,
17
18
  role: :role,
18
19
  roles: :roles,
19
20
  groups: :groups,
@@ -31,6 +32,7 @@ module WorkOS
31
32
  :email,
32
33
  :first_name,
33
34
  :last_name,
35
+ :name,
34
36
  :role,
35
37
  :roles,
36
38
  :groups,
@@ -48,6 +50,7 @@ module WorkOS
48
50
  @email = hash[:email]
49
51
  @first_name = hash[:first_name]
50
52
  @last_name = hash[:last_name]
53
+ @name = hash[:name]
51
54
  @role = hash[:role] ? WorkOS::SlimRole.new(hash[:role]) : nil
52
55
  @roles = (hash[:roles] || []).map { |item| item ? WorkOS::SlimRole.new(item) : nil }
53
56
  @groups = hash[:groups] || []