descope 1.0.6 → 1.1.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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +51 -12
  3. data/.github/workflows/publish-gem.yaml +6 -26
  4. data/.github/workflows/release-please.yaml +36 -0
  5. data/.gitignore +5 -2
  6. data/.release-please-manifest.json +1 -1
  7. data/.ruby-version +1 -1
  8. data/CHANGELOG.md +21 -0
  9. data/Gemfile +8 -7
  10. data/Gemfile.lock +70 -56
  11. data/README.md +170 -51
  12. data/examples/ruby-on-rails-api/descope/Gemfile +8 -8
  13. data/examples/ruby-on-rails-api/descope/Gemfile.lock +1 -1
  14. data/examples/ruby-on-rails-api/descope/package-lock.json +203 -141
  15. data/examples/ruby-on-rails-api/descope/package.json +1 -1
  16. data/examples/ruby-on-rails-api/descope/yarn.lock +185 -87
  17. data/lib/descope/api/v1/auth/enchantedlink.rb +3 -1
  18. data/lib/descope/api/v1/auth/magiclink.rb +3 -1
  19. data/lib/descope/api/v1/auth/otp.rb +3 -1
  20. data/lib/descope/api/v1/auth/password.rb +6 -2
  21. data/lib/descope/api/v1/auth/totp.rb +3 -1
  22. data/lib/descope/api/v1/auth.rb +47 -12
  23. data/lib/descope/api/v1/management/common.rb +20 -5
  24. data/lib/descope/api/v1/management/sso_application.rb +236 -0
  25. data/lib/descope/api/v1/management/sso_settings.rb +2 -24
  26. data/lib/descope/api/v1/management/user.rb +151 -13
  27. data/lib/descope/api/v1/management.rb +2 -0
  28. data/lib/descope/api/v1/session.rb +37 -4
  29. data/lib/descope/mixins/common.rb +1 -0
  30. data/lib/descope/mixins/http.rb +60 -9
  31. data/lib/descope/mixins/initializer.rb +5 -2
  32. data/lib/descope/mixins/logging.rb +12 -4
  33. data/lib/descope/version.rb +1 -1
  34. data/spec/descope/api/v1/auth_spec.rb +29 -0
  35. data/spec/descope/api/v1/auth_token_extraction_spec.rb +126 -0
  36. data/spec/descope/api/v1/session_refresh_spec.rb +98 -0
  37. data/spec/factories/user.rb +1 -1
  38. data/spec/integration/lib.descope/api/v1/auth/enchantedlink_spec.rb +20 -22
  39. data/spec/integration/lib.descope/api/v1/auth/magiclink_spec.rb +6 -2
  40. data/spec/integration/lib.descope/api/v1/auth/otp_spec.rb +6 -2
  41. data/spec/integration/lib.descope/api/v1/auth/session_spec.rb +68 -0
  42. data/spec/integration/lib.descope/api/v1/auth/totp_spec.rb +6 -2
  43. data/spec/integration/lib.descope/api/v1/management/access_key_spec.rb +12 -1
  44. data/spec/integration/lib.descope/api/v1/management/audit_spec.rb +5 -3
  45. data/spec/integration/lib.descope/api/v1/management/authz_spec.rb +28 -5
  46. data/spec/integration/lib.descope/api/v1/management/flow_spec.rb +3 -1
  47. data/spec/integration/lib.descope/api/v1/management/permissions_spec.rb +22 -2
  48. data/spec/integration/lib.descope/api/v1/management/project_spec.rb +18 -2
  49. data/spec/integration/lib.descope/api/v1/management/roles_spec.rb +116 -36
  50. data/spec/integration/lib.descope/api/v1/management/user_spec.rb +74 -8
  51. data/spec/lib.descope/api/v1/auth/enchantedlink_spec.rb +11 -2
  52. data/spec/lib.descope/api/v1/auth/password_spec.rb +10 -1
  53. data/spec/lib.descope/api/v1/auth_spec.rb +167 -5
  54. data/spec/lib.descope/api/v1/cookie_domain_fix_integration_spec.rb +245 -0
  55. data/spec/lib.descope/api/v1/management/sso_application_spec.rb +217 -0
  56. data/spec/lib.descope/api/v1/management/sso_settings_spec.rb +2 -2
  57. data/spec/lib.descope/api/v1/management/user_spec.rb +134 -46
  58. data/spec/lib.descope/api/v1/session_spec.rb +119 -6
  59. data/spec/lib.descope/mixins/http_spec.rb +229 -0
  60. data/spec/support/client_config.rb +0 -1
  61. data/spec/support/utils.rb +21 -0
  62. metadata +14 -8
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Descope
4
+ module Api
5
+ module V1
6
+ module Management
7
+ # Management API calls
8
+ module SSOApplication
9
+ include Descope::Api::V1::Management::Common
10
+
11
+ def create_sso_oidc_app(id: nil, name: nil, description: nil, enabled: nil, logo: nil, login_page_url: nil)
12
+ # Create a new OIDC sso application with the given name. SSO application IDs are provisioned automatically,
13
+ # but can be provided explicitly if needed. Both the name and ID must be unique per project.
14
+ body = {}
15
+ body[:id] = id if id
16
+ body[:name] = name if name
17
+ body[:description] = description if description
18
+ body[:enabled] = enabled if enabled
19
+ body[:logo] = logo if logo
20
+ body[:loginPageUrl] = login_page_url if login_page_url
21
+ post(SSO_APPLICATION_OIDC_CREATE_PATH, body)
22
+ end
23
+
24
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
25
+ def create_saml_application(
26
+ name: nil,
27
+ login_page_url: nil,
28
+ id: nil,
29
+ description: nil,
30
+ logo: nil,
31
+ enabled: nil,
32
+ use_metadata_info: nil,
33
+ metadata_url: nil,
34
+ entity_id: nil,
35
+ acs_url: nil,
36
+ certificate: nil,
37
+ attribute_mapping: nil,
38
+ groups_mapping: nil,
39
+ acs_allowed_callbacks: nil,
40
+ subject_name_id_type: nil,
41
+ subject_name_id_format: nil,
42
+ default_relay_state: nil,
43
+ force_authentication: nil,
44
+ logout_redirect_url: nil
45
+ )
46
+ # Create a new SAML sso application with the given name. SSO application IDs are provisioned automatically,
47
+ # but can be provided explicitly if needed. Both the name and ID must be unique per project.
48
+
49
+ if use_metadata_info
50
+ raise Descope::ArgumentException.new('metadata_url argument must be set', code: 400) unless metadata_url
51
+ else
52
+ unless entity_id && acs_url && certificate
53
+ raise Descope::ArgumentException.new('entity_id, acs_url, certificate arguments must be set', code: 400)
54
+ end
55
+ end
56
+
57
+ attribute_mapping ||= []
58
+ groups_mapping ||= []
59
+ acs_allowed_callbacks ||= []
60
+ body = compose_create_update_saml_body(
61
+ name:,
62
+ login_page_url:,
63
+ id:,
64
+ description:,
65
+ enabled:,
66
+ logo:,
67
+ use_metadata_info:,
68
+ metadata_url:,
69
+ entity_id:,
70
+ acs_url:,
71
+ certificate:,
72
+ attribute_mapping:,
73
+ groups_mapping:,
74
+ acs_allowed_callbacks:,
75
+ subject_name_id_type:,
76
+ subject_name_id_format:,
77
+ default_relay_state:,
78
+ force_authentication:,
79
+ logout_redirect_url:
80
+ )
81
+ post(SSO_APPLICATION_SAML_CREATE_PATH, body)
82
+ end
83
+
84
+ def update_sso_oidc_app(id: nil, name: nil, description: nil, enabled: nil, logo: nil, login_page_url: nil, force_authentication: nil)
85
+ # Update an existing OIDC sso application with the given parameters. IMPORTANT: All parameters are used as overrides
86
+ # to the existing sso application. Empty fields will override populated fields. Use carefully.
87
+ body = compose_create_update_oidc_body(name, login_page_url, id, description, enabled, logo, force_authentication)
88
+ post(SSO_APPLICATION_OIDC_UPDATE_PATH, body)
89
+ end
90
+
91
+ def update_saml_application(
92
+ id: nil,
93
+ name: nil,
94
+ login_page_url: nil,
95
+ description: nil,
96
+ logo: nil,
97
+ enabled: nil,
98
+ use_metadata_info: nil,
99
+ metadata_url: nil,
100
+ entity_id: nil,
101
+ acs_url: nil,
102
+ certificate: nil,
103
+ attribute_mapping: nil,
104
+ groups_mapping: nil,
105
+ acs_allowed_callbacks: nil,
106
+ subject_name_id_type: nil,
107
+ subject_name_id_format: nil,
108
+ default_relay_state: nil,
109
+ force_authentication: nil,
110
+ logout_redirect_url: nil
111
+ )
112
+ # Update an existing SAML sso application with the given parameters. IMPORTANT: All parameters are used as overrides
113
+ # to the existing sso application. Empty fields will override populated fields. Use carefully.
114
+
115
+ if use_metadata_info
116
+ raise 'metadata_url argument must be set' unless metadata_url
117
+ else
118
+ raise 'entity_id, acs_url, certificate arguments must be set' unless entity_id && acs_url
119
+ end
120
+
121
+ attribute_mapping ||= []
122
+ groups_mapping ||= []
123
+ acs_allowed_callbacks ||= []
124
+
125
+ body = compose_create_update_saml_body(
126
+ name:,
127
+ login_page_url:,
128
+ id:,
129
+ description:,
130
+ enabled:,
131
+ logo:,
132
+ use_metadata_info:,
133
+ metadata_url:,
134
+ entity_id:,
135
+ acs_url:,
136
+ certificate:,
137
+ attribute_mapping:,
138
+ groups_mapping:,
139
+ acs_allowed_callbacks:,
140
+ subject_name_id_type:,
141
+ subject_name_id_format:,
142
+ default_relay_state:,
143
+ force_authentication:,
144
+ logout_redirect_url:
145
+ )
146
+ post(SSO_APPLICATION_SAML_UPDATE_PATH, body)
147
+ end
148
+
149
+ def delete_sso_app(id)
150
+ # Delete an existing sso application. IMPORTANT: This operation is irreversible. Use carefully.
151
+ delete(SSO_APPLICATION_DELETE_PATH, { id: })
152
+ end
153
+
154
+ def load_sso_app(id)
155
+ # Load an existing sso application.
156
+ get(SSO_APPLICATION_LOAD_PATH, { id: })
157
+ end
158
+
159
+ def load_all_sso_apps
160
+ # Load all sso applications.
161
+ #
162
+ # Return value:
163
+ # {
164
+ # "apps": [
165
+ # {"id":"app1","name":"<name>","description":"<description>","enabled":true,"logo":"","appType":"saml","samlSettings":{"loginPageUrl":"","idpCert":"<cert>","useMetadataInfo":true,"metadataUrl":"","entityId":"","acsUrl":"","certificate":"","attributeMapping":[{"name":"email","type":"","value":"attrVal1"}],"groupsMapping":[{"name":"grp1","type":"","filterType":"roles","value":"","roles":[{"id":"myRoleId","name":"myRole"}]}],"idpMetadataUrl":"","idpEntityId":"","idpSsoUrl":"","acsAllowedCallbacks":[],"subjectNameIdType":"","subjectNameIdFormat":"", "defaultRelayState":"", "forceAuthentication": false, "idpLogoutUrl": "", "logoutRedirectUrl": ""},"oidcSettings":{"loginPageUrl":"","issuer":"","discoveryUrl":"", "forceAuthentication":false}},
166
+ # {"id":"app2","name":"<name>","description":"<description>","enabled":true,"logo":"","appType":"saml","samlSettings":{"loginPageUrl":"","idpCert":"<cert>","useMetadataInfo":true,"metadataUrl":"","entityId":"","acsUrl":"","certificate":"","attributeMapping":[{"name":"email","type":"","value":"attrVal1"}],"groupsMapping":[{"name":"grp1","type":"","filterType":"roles","value":"","roles":[{"id":"myRoleId","name":"myRole"}]}],"idpMetadataUrl":"","idpEntityId":"","idpSsoUrl":"","acsAllowedCallbacks":[],"subjectNameIdType":"","subjectNameIdFormat":"", "defaultRelayState":"", "forceAuthentication": false, "idpLogoutUrl": "", "logoutRedirectUrl": ""},"oidcSettings":{"loginPageUrl":"","issuer":"","discoveryUrl":"", "forceAuthentication":false}}
167
+ # ]
168
+ # }
169
+ # Containing the loaded sso applications information.
170
+ get(SSO_APPLICATION_LOAD_ALL_PATH, {})
171
+ end
172
+
173
+ private
174
+
175
+ def compose_create_update_oidc_body(name, login_page_url, id, description, enabled, logo, force_authentication)
176
+ body = {}
177
+ body[:name] = name if name
178
+ body[:loginPageUrl] = login_page_url if login_page_url
179
+ body[:id] = id if id
180
+ body[:description] = description if description
181
+ body[:logo] = logo if logo
182
+ body[:enabled] = enabled if enabled
183
+ body[:force_authentication] = force_authentication if force_authentication
184
+ body
185
+ end
186
+
187
+ # rubocop:disable Metrics/AbcSize
188
+ def compose_create_update_saml_body(
189
+ name: nil,
190
+ login_page_url: nil,
191
+ id: nil,
192
+ description: nil,
193
+ enabled: nil,
194
+ logo: nil,
195
+ use_metadata_info: nil,
196
+ metadata_url: nil,
197
+ entity_id: nil,
198
+ acs_url: nil,
199
+ certificate: nil,
200
+ attribute_mapping: nil,
201
+ groups_mapping: nil,
202
+ acs_allowed_callbacks: nil,
203
+ subject_name_id_type: nil,
204
+ subject_name_id_format: nil,
205
+ default_relay_state: nil,
206
+ force_authentication: nil,
207
+ logout_redirect_url: nil
208
+ )
209
+ body = {}
210
+ body[:name] = name if name
211
+ body[:loginPageUrl] = login_page_url if login_page_url
212
+ body[:id] = id if id
213
+ body[:description] = description if description
214
+ body[:enabled] = enabled if enabled
215
+ body[:logo] = logo if logo
216
+ body[:useMetadataInfo] = use_metadata_info if use_metadata_info
217
+ body[:metadataUrl] = metadata_url if metadata_url
218
+ body[:entityId] = entity_id if entity_id
219
+ body[:acsUrl] = acs_url if acs_url
220
+ body[:certificate] = certificate if certificate
221
+ body[:attributeMapping] = attribute_mapping if attribute_mapping
222
+ body[:groupsMapping] = groups_mapping if groups_mapping
223
+ body[:acsAllowedCallbacks] = acs_allowed_callbacks if acs_allowed_callbacks
224
+ body[:subjectNameIdType] = subject_name_id_type if subject_name_id_type
225
+ body[:subjectNameIdFormat] = subject_name_id_format if subject_name_id_format
226
+ body[:defaultRelayState] = default_relay_state if default_relay_state
227
+ body[:forceAuthentication] = force_authentication if force_authentication
228
+ body[:logoutRedirectUrl] = logout_redirect_url if logout_redirect_url
229
+ puts "body: #{body}"
230
+ body
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
@@ -18,28 +18,6 @@ module Descope
18
18
  delete(SSO_SETTINGS_PATH, { tenantId: tenant_id })
19
19
  end
20
20
 
21
- def create_sso_oidc_app(id: nil, name: nil, description: nil, enabled: nil, logo: nil, login_page_url: nil)
22
- body = {}
23
- body[:id] = id if id
24
- body[:name] = name if name
25
- body[:description] = description if description
26
- body[:enabled] = enabled if enabled
27
- body[:logo] = logo if logo
28
- body[:loginPageUrl] = login_page_url if login_page_url
29
- post(SSO_OIDC_CREATE_APP_PATH, body)
30
- end
31
-
32
- def update_sso_oidc_app(id: nil, name: nil, description: nil, enabled: nil, logo: nil, login_page_url: nil)
33
- body = {}
34
- body[:id] = id if id
35
- body[:name] = name if name
36
- body[:description] = description if description
37
- body[:enabled] = enabled if enabled
38
- body[:logo] = logo if logo
39
- body[:loginPageUrl] = login_page_url if login_page_url
40
- put(SSO_OIDC_UPDATE_APP_PATH, body)
41
- end
42
-
43
21
  def configure_sso_oidc(tenant_id: nil, settings: nil, redirect_url: nil, domain: nil)
44
22
  raise Descope::ArgumentException.new('SSO settings must be a Hash', code: 400) unless settings.is_a?(Hash)
45
23
 
@@ -63,12 +41,12 @@ module Descope
63
41
  redirectUrl: redirect_url,
64
42
  domain:
65
43
  }
66
- post(SSO_SAML_PATH, request_params)
44
+ post(SSO_SETTINGS_PATH, request_params)
67
45
  end
68
46
 
69
47
  def configure_sso_saml_metadata(tenant_id: nil, settings: nil, redirect_url: nil, domain: nil)
70
48
  # Configure tenant SSO SAML Metadata, using a valid management key.
71
- post(SSO_SAML_METADATA_PATH, compose_metadata_body(tenant_id, settings, redirect_url, domain))
49
+ post(SSO_METADATA_PATH, compose_metadata_body(tenant_id, settings, redirect_url, domain))
72
50
  end
73
51
 
74
52
  private
@@ -185,7 +185,9 @@ module Descope
185
185
  statuses: [],
186
186
  emails: [],
187
187
  phones: [],
188
- sso_app_ids: []
188
+ sso_app_ids: [],
189
+ tenant_role_ids: {},
190
+ tenant_role_names: {}
189
191
  )
190
192
  body = {
191
193
  loginId: login_id,
@@ -212,9 +214,18 @@ module Descope
212
214
  body[:ssoAppIds] = sso_app_ids unless sso_app_ids.empty?
213
215
  body[:tenantIds] = tenant_ids unless tenant_ids.empty?
214
216
  body[:roleNames] = role_names unless role_names.empty?
217
+ body[:tenantRoleIds] = map_to_values_object(tenant_role_ids) unless tenant_role_ids.nil? || tenant_role_ids.empty?
218
+ body[:tenantRoleNames] = map_to_values_object(tenant_role_names) unless tenant_role_names.nil? || tenant_role_names.empty?
215
219
  post(Common::USERS_SEARCH_PATH, body)
216
220
  end
217
221
 
222
+ def map_to_values_object(input_map)
223
+ return {} unless input_map.is_a?(Hash)
224
+ input_map.each_with_object({}) do |(key, values), result|
225
+ result[key] = { values: Array(values) }
226
+ end
227
+ end
228
+
218
229
  # Get an existing user's provider token, using a valid management key.
219
230
  # @see https://docs.descope.com/api/openapi/usermanagement/operation/GetUserProviderToken/
220
231
  def get_provider_token(login_id: nil, provider: nil)
@@ -324,6 +335,51 @@ module Descope
324
335
  post(Common::USER_UPDATE_CUSTOM_ATTRIBUTE_PATH, body)
325
336
  end
326
337
 
338
+ def patch_user(
339
+ login_id: nil,
340
+ email: nil,
341
+ phone: nil,
342
+ name: nil,
343
+ given_name: nil,
344
+ middle_name: nil,
345
+ family_name: nil,
346
+ role_names: [],
347
+ user_tenants: [],
348
+ picture: nil,
349
+ custom_attributes: nil,
350
+ verified_email: nil,
351
+ verified_phone: nil,
352
+ additional_identifiers: [],
353
+ password: nil,
354
+ hashed_password: {},
355
+ sso_app_ids: []
356
+ )
357
+ validate_login_id(login_id)
358
+ role_names ||= []
359
+ user_tenants ||= []
360
+ path = Common::USER_PATCH_PATH
361
+ request_params = user_compose_update_body(
362
+ login_id:,
363
+ email:,
364
+ phone:,
365
+ name:,
366
+ given_name:,
367
+ middle_name:,
368
+ family_name:,
369
+ role_names:,
370
+ user_tenants:,
371
+ picture:,
372
+ custom_attributes:,
373
+ verified_email:,
374
+ verified_phone:,
375
+ additional_identifiers:,
376
+ password:,
377
+ hashed_password:,
378
+ sso_app_ids:
379
+ )
380
+ patch(path, request_params)
381
+ end
382
+
327
383
  def update_jwt(jwt: nil, custom_claims: nil)
328
384
  body = {
329
385
  jwt:,
@@ -460,6 +516,75 @@ module Descope
460
516
  post(USER_GENERATE_EMBEDDED_LINK_PATH, request_params)
461
517
  end
462
518
 
519
+ # Search for all test users.
520
+ #
521
+ # @param tenant_ids [Array<String>] Optional list of tenant IDs to filter by
522
+ # @param role_names [Array<String>] Optional list of role names to filter by
523
+ # @param limit [Integer] Optional limit of the number of users returned. Leave empty for default.
524
+ # @param page [Integer] Optional pagination control. Pages start at 0 and must be non-negative.
525
+ # @param custom_attributes [Hash] Optional search for an attribute with a given value
526
+ # @param statuses [Array<String>] Optional list of statuses to search for ("enabled", "disabled", "invited")
527
+ # @param emails [Array<String>] Optional list of emails to search for
528
+ # @param phones [Array<String>] Optional list of phones to search for
529
+ # @param sso_app_ids [Array<String>] Optional list of SSO application IDs to filter by
530
+ # @param text [String] Optional string, allows free text search among all user's attributes.
531
+ # @param login_ids [Array<String>] Optional list of login ids
532
+ # @param sort [Array<Hash>] Optional array, allows to sort by fields.
533
+ #
534
+ # @return [Hash] Return hash in the format {"users": []}
535
+ #
536
+ # The "users" key contains a list of all the found users and their information
537
+ #
538
+ # @raise [AuthException] Raised if the search operation fails
539
+ #
540
+ def search_all_test_users(
541
+ tenant_ids: [],
542
+ role_names: [],
543
+ limit: 0,
544
+ page: 0,
545
+ custom_attributes: {},
546
+ statuses: [],
547
+ emails: [],
548
+ phones: [],
549
+ sso_app_ids: [],
550
+ sort: [],
551
+ text: nil,
552
+ login_ids: [],
553
+ tenant_role_ids: {},
554
+ tenant_role_names: {}
555
+ )
556
+ tenant_ids ||= []
557
+ role_names ||= []
558
+
559
+ if limit < 0 || page < 0
560
+ raise Descope::ArgumentException.new(
561
+ 'limit or page must be non-negative', code: 400
562
+ )
563
+ end
564
+
565
+ body = {
566
+ tenantIds: tenant_ids,
567
+ roleNames: role_names,
568
+ limit:,
569
+ page:,
570
+ testUsersOnly: true,
571
+ withTestUser: true
572
+ }
573
+
574
+ body[:statuses] = statuses unless statuses.nil? || statuses.empty?
575
+ body[:emails] = emails unless emails.nil? || emails.empty?
576
+ body[:phones] = phones unless phones.nil? || phones.empty?
577
+ body[:customAttributes] = custom_attributes unless custom_attributes.nil? || custom_attributes.empty?
578
+ body[:ssoAppIds] = sso_app_ids unless sso_app_ids.nil? || sso_app_ids.empty?
579
+ body[:loginIds] = login_ids unless login_ids.nil? || login_ids.empty?
580
+ body[:sort] = sort unless sort.nil? || sort.empty?
581
+ body[:text] = text unless text.nil? || text.empty?
582
+ body[:tenantRoleIds] = map_to_values_object(tenant_role_ids) unless tenant_role_ids.nil? || tenant_role_ids.empty?
583
+ body[:tenantRoleNames] = map_to_values_object(tenant_role_names) unless tenant_role_names.nil? || tenant_role_names.empty?
584
+
585
+ post(Common::TEST_USERS_SEARCH_PATH, body)
586
+ end
587
+
463
588
 
464
589
  private
465
590
 
@@ -486,11 +611,15 @@ module Descope
486
611
  middle_name: nil,
487
612
  family_name: nil,
488
613
  sso_app_ids: [],
489
- skip_create: false
614
+ skip_create: false,
615
+ template_id: nil
490
616
  )
491
617
  role_names ||= []
492
618
  user_tenants ||= []
493
619
  path = Common::USER_CREATE_PATH
620
+
621
+ path = Common::TEST_USER_CREATE_PATH if test
622
+
494
623
  request_params = user_compose_create_body(
495
624
  login_id:,
496
625
  email:,
@@ -513,7 +642,8 @@ module Descope
513
642
  additional_identifiers:,
514
643
  password:,
515
644
  hashed_password:,
516
- sso_app_ids:
645
+ sso_app_ids:,
646
+ template_id:,
517
647
  )
518
648
  return request_params if skip_create
519
649
 
@@ -542,7 +672,8 @@ module Descope
542
672
  additional_identifiers: [],
543
673
  password: nil,
544
674
  hashed_password: {},
545
- sso_app_ids: []
675
+ sso_app_ids: [],
676
+ template_id: nil
546
677
  )
547
678
  body = user_compose_update_body(
548
679
  login_id:,
@@ -561,14 +692,15 @@ module Descope
561
692
  additional_identifiers:,
562
693
  password:,
563
694
  hashed_password:,
564
- sso_app_ids:
695
+ sso_app_ids:,
696
+ template_id:,
565
697
  )
566
698
  body[:invite] = invite
567
- body[:verifiedEmail] = verified_email unless verified_email.nil? || !verified_email.empty?
568
- body[:verifiedPhone] = verified_phone unless verified_phone.nil? || !verified_phone.empty?
569
- body[:inviteUrl] = invite_url unless invite_url.nil? || !invite_url.empty?
570
- body[:sendMail] = send_mail unless send_mail.nil? || !send_mail.empty?
571
- body[:sendSMS] = send_sms unless send_sms.nil? || !send_sms.empty?
699
+ body[:verifiedEmail] = verified_email unless verified_email.nil? || verified_email.empty?
700
+ body[:verifiedPhone] = verified_phone unless verified_phone.nil? || verified_phone.empty?
701
+ body[:inviteUrl] = invite_url unless invite_url.nil? || invite_url.empty?
702
+ body[:sendMail] = send_mail unless send_mail.nil? || send_mail.empty?
703
+ body[:sendSMS] = send_sms unless send_sms.nil? || send_sms.empty?
572
704
 
573
705
  body
574
706
  end
@@ -592,7 +724,8 @@ module Descope
592
724
  additional_identifiers: [],
593
725
  password: nil,
594
726
  hashed_password: {},
595
- sso_app_ids: []
727
+ sso_app_ids: [],
728
+ template_id: nil
596
729
  )
597
730
  body = {
598
731
  loginId: login_id,
@@ -615,18 +748,23 @@ module Descope
615
748
  body[:phone] = phone unless phone.nil? || phone.empty?
616
749
  body[:name] = name unless name.nil? || name.empty?
617
750
  body[:roleNames] = role_names unless role_names.nil? || role_names.empty?
618
- body[:userTenants] = associated_tenants_to_hash_array(user_tenants) unless user_tenants.nil? || user_tenants.empty?
751
+ unless user_tenants.nil? || user_tenants.empty?
752
+ body[:userTenants] = associated_tenants_to_hash_array(user_tenants)
753
+ end
619
754
  body[:test] = test unless test.nil?
620
755
  body[:invite] = invite unless invite.nil?
621
756
  body[:picture] = picture unless picture.nil? || picture.empty?
622
757
  body[:customAttributes] = custom_attributes unless custom_attributes.nil? || custom_attributes.empty?
623
- body[:additionalIdentifiers] = additional_identifiers unless additional_identifiers.nil? || additional_identifiers.empty?
758
+ unless additional_identifiers.nil? || additional_identifiers.empty?
759
+ body[:additionalIdentifiers] = additional_identifiers
760
+ end
624
761
  body[:ssoAppIds] = sso_app_ids unless sso_app_ids.nil? || sso_app_ids.empty?
625
762
  body[:verifiedEmail] = verified_email unless verified_email.nil? || !verified_email.to_s.empty?
626
763
  body[:givenName] = given_name unless given_name.nil?
627
764
  body[:middleName] = middle_name unless middle_name.nil?
628
765
  body[:familyName] = family_name unless family_name.nil?
629
766
  body[:verifiedPhone] = verified_phone unless verified_phone.nil?
767
+ body[:templateId] = template_id unless template_id.nil? || template_id.empty?
630
768
  body
631
769
  end
632
770
  end
@@ -10,6 +10,7 @@ require 'descope/api/v1/management/role'
10
10
  require 'descope/api/v1/management/project'
11
11
  require 'descope/api/v1/management/authz'
12
12
  require 'descope/api/v1/management/audit'
13
+ require 'descope/api/v1/management/sso_application'
13
14
  require 'descope/api/v1/management/sso_settings'
14
15
  require 'descope/api/v1/management/scim'
15
16
  require 'descope/api/v1/management/password'
@@ -29,6 +30,7 @@ module Descope
29
30
  include Descope::Api::V1::Management::Project
30
31
  include Descope::Api::V1::Management::Authz
31
32
  include Descope::Api::V1::Management::Audit
33
+ include Descope::Api::V1::Management::SSOApplication
32
34
  include Descope::Api::V1::Management::SSOSettings
33
35
  include Descope::Api::V1::Management::SCIM
34
36
  include Descope::Api::V1::Management::Password
@@ -20,11 +20,28 @@ module Descope
20
20
  # [amr, drn, exp, iss, rexp, sub, jwt] in the top level of the response dict, please use
21
21
  # them from the sessionToken key instead, as these claims will soon be deprecated from the top level
22
22
  # of the response dict.
23
-
23
+ # Make sure you set Enable refresh token rotation in the Project Settings before using this.
24
24
  validate_refresh_token_not_nil(refresh_token)
25
25
  validate_token(refresh_token, audience)
26
26
  res = post(REFRESH_TOKEN_PATH, {}, {}, refresh_token)
27
- generate_jwt_response(response_body: res, refresh_cookie: refresh_token, audience:)
27
+ cookies = res.fetch(COOKIE_DATA_NAME, nil) || res.fetch('cookies', {})
28
+
29
+ # Check each source and use the first non-empty value
30
+ refresh_cookie = nil
31
+ cookie_value = cookies.fetch(REFRESH_SESSION_COOKIE_NAME, nil)
32
+ if cookie_value && !cookie_value.empty?
33
+ refresh_cookie = cookie_value
34
+ else
35
+ jwt_value = res.fetch('refreshJwt', nil)
36
+ if jwt_value && !jwt_value.empty?
37
+ refresh_cookie = jwt_value
38
+ else
39
+ refresh_cookie = refresh_token
40
+ end
41
+ end
42
+
43
+ logger.debug("Refreshing refresh_cookie: #{refresh_cookie}")
44
+ generate_jwt_response(response_body: res, refresh_cookie: refresh_cookie, audience: audience)
28
45
  end
29
46
 
30
47
  def me(refresh_token = nil)
@@ -72,12 +89,28 @@ module Descope
72
89
 
73
90
  begin
74
91
  @logger.debug("Validating session token: #{session_token}")
75
- validate_session(session_token:, audience:)
92
+ validate_session(session_token: session_token, audience: audience)
76
93
  rescue Descope::AuthException
77
94
  @logger.debug("Session is invalid, refreshing session with refresh token: #{refresh_token}")
78
- refresh_session(refresh_token:, audience:)
95
+ refresh_session(refresh_token: refresh_token, audience: audience)
79
96
  end
80
97
  end
98
+
99
+ def history(refresh_token = nil)
100
+ # Retrieve user authentication history for the refresh token
101
+ # Return List in the format
102
+ # [
103
+ # {
104
+ # "userId": "User's ID",
105
+ # "loginTime": "User'sLogin time",
106
+ # "city": "User's city",
107
+ # "country": "User's country",
108
+ # "ip": User's IP
109
+ # }
110
+ # ]
111
+ validate_refresh_token_not_nil(refresh_token)
112
+ get(HISTORY_PATH, {}, {}, refresh_token)
113
+ end
81
114
  end
82
115
  end
83
116
  end
@@ -52,6 +52,7 @@ module Descope
52
52
  LOGOUT_ALL_PATH = '/v1/auth/logoutall'
53
53
  VALIDATE_SESSION_PATH = '/v1/auth/validate'
54
54
  ME_PATH = '/v1/auth/me'
55
+ HISTORY_PATH = '/v1/auth/me/history'
55
56
 
56
57
  # access key
57
58
  EXCHANGE_AUTH_ACCESS_KEY_PATH = '/v1/auth/accesskey/exchange'