descope 1.0.7 → 1.2.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +51 -12
  3. data/.github/workflows/publish-gem.yaml +6 -33
  4. data/.github/workflows/release-please.yaml +36 -0
  5. data/.gitignore +3 -2
  6. data/.release-please-manifest.json +1 -1
  7. data/.rubocop.yml +1 -1
  8. data/.rubocop_todo.yml +1 -1
  9. data/.ruby-version +1 -1
  10. data/CHANGELOG.md +31 -0
  11. data/Gemfile +7 -6
  12. data/Gemfile.lock +46 -45
  13. data/README.md +244 -1
  14. data/examples/ruby-on-rails-api/descope/package-lock.json +16 -10
  15. data/examples/ruby-on-rails-api/descope/yarn.lock +3 -3
  16. data/lib/descope/api/v1/auth/enchantedlink.rb +76 -9
  17. data/lib/descope/api/v1/management/access_key.rb +8 -7
  18. data/lib/descope/api/v1/management/analytics.rb +62 -0
  19. data/lib/descope/api/v1/management/audit.rb +2 -2
  20. data/lib/descope/api/v1/management/authz.rb +15 -15
  21. data/lib/descope/api/v1/management/common.rb +76 -0
  22. data/lib/descope/api/v1/management/descoper.rb +45 -0
  23. data/lib/descope/api/v1/management/engine.rb +44 -0
  24. data/lib/descope/api/v1/management/fga.rb +64 -0
  25. data/lib/descope/api/v1/management/flow.rb +5 -5
  26. data/lib/descope/api/v1/management/group.rb +42 -0
  27. data/lib/descope/api/v1/management/jwt_template.rb +49 -0
  28. data/lib/descope/api/v1/management/lists.rb +96 -0
  29. data/lib/descope/api/v1/management/management_key.rb +54 -0
  30. data/lib/descope/api/v1/management/outbound_app.rb +112 -0
  31. data/lib/descope/api/v1/management/password.rb +2 -2
  32. data/lib/descope/api/v1/management/permission.rb +4 -4
  33. data/lib/descope/api/v1/management/project.rb +5 -5
  34. data/lib/descope/api/v1/management/role.rb +5 -5
  35. data/lib/descope/api/v1/management/scim.rb +14 -14
  36. data/lib/descope/api/v1/management/scope_claim_mapping.rb +34 -0
  37. data/lib/descope/api/v1/management/sso_application.rb +7 -7
  38. data/lib/descope/api/v1/management/sso_settings.rb +5 -5
  39. data/lib/descope/api/v1/management/tenant.rb +8 -6
  40. data/lib/descope/api/v1/management/third_party_application.rb +190 -0
  41. data/lib/descope/api/v1/management/user.rb +36 -36
  42. data/lib/descope/api/v1/management.rb +22 -0
  43. data/lib/descope/http_client.rb +30 -0
  44. data/lib/descope/mixins/common.rb +1 -0
  45. data/lib/descope/mixins/http.rb +25 -18
  46. data/lib/descope/mixins/initializer.rb +23 -11
  47. data/lib/descope/mixins/requests.rb +24 -0
  48. data/lib/descope/mixins.rb +2 -1
  49. data/lib/descope/version.rb +1 -1
  50. data/spec/integration/lib.descope/api/v1/auth/enchantedlink_spec.rb +19 -21
  51. data/spec/integration/lib.descope/api/v1/auth/magiclink_spec.rb +5 -1
  52. data/spec/integration/lib.descope/api/v1/auth/otp_spec.rb +5 -1
  53. data/spec/integration/lib.descope/api/v1/auth/session_spec.rb +24 -5
  54. data/spec/integration/lib.descope/api/v1/auth/totp_spec.rb +5 -1
  55. data/spec/integration/lib.descope/api/v1/management/access_key_spec.rb +10 -2
  56. data/spec/integration/lib.descope/api/v1/management/authz_spec.rb +26 -5
  57. data/spec/integration/lib.descope/api/v1/management/permissions_spec.rb +20 -2
  58. data/spec/integration/lib.descope/api/v1/management/project_spec.rb +16 -2
  59. data/spec/integration/lib.descope/api/v1/management/roles_spec.rb +114 -36
  60. data/spec/integration/lib.descope/api/v1/management/user_spec.rb +21 -4
  61. data/spec/lib.descope/api/v1/auth/enchantedlink_spec.rb +163 -1
  62. data/spec/lib.descope/api/v1/management/access_key_spec.rb +7 -7
  63. data/spec/lib.descope/api/v1/management/analytics_spec.rb +82 -0
  64. data/spec/lib.descope/api/v1/management/audit_spec.rb +2 -2
  65. data/spec/lib.descope/api/v1/management/authz_spec.rb +16 -16
  66. data/spec/lib.descope/api/v1/management/descoper_spec.rb +101 -0
  67. data/spec/lib.descope/api/v1/management/engine_spec.rb +115 -0
  68. data/spec/lib.descope/api/v1/management/fga_spec.rb +169 -0
  69. data/spec/lib.descope/api/v1/management/flow_spec.rb +5 -5
  70. data/spec/lib.descope/api/v1/management/group_spec.rb +72 -0
  71. data/spec/lib.descope/api/v1/management/jwt_template_spec.rb +103 -0
  72. data/spec/lib.descope/api/v1/management/lists_spec.rb +285 -0
  73. data/spec/lib.descope/api/v1/management/management_key_spec.rb +109 -0
  74. data/spec/lib.descope/api/v1/management/outbound_app_spec.rb +236 -0
  75. data/spec/lib.descope/api/v1/management/password_spec.rb +2 -2
  76. data/spec/lib.descope/api/v1/management/permission_spec.rb +4 -4
  77. data/spec/lib.descope/api/v1/management/project_spec.rb +4 -4
  78. data/spec/lib.descope/api/v1/management/role_spec.rb +5 -5
  79. data/spec/lib.descope/api/v1/management/scim_spec.rb +14 -14
  80. data/spec/lib.descope/api/v1/management/scope_claim_mapping_spec.rb +59 -0
  81. data/spec/lib.descope/api/v1/management/sso_application_spec.rb +6 -6
  82. data/spec/lib.descope/api/v1/management/sso_settings_spec.rb +5 -5
  83. data/spec/lib.descope/api/v1/management/tenant_spec.rb +6 -6
  84. data/spec/lib.descope/api/v1/management/third_party_application_spec.rb +171 -0
  85. data/spec/lib.descope/api/v1/management/user_spec.rb +36 -36
  86. data/spec/lib.descope/auth_management_key_spec.rb +151 -0
  87. data/spec/lib.descope/mixins/http_spec.rb +17 -6
  88. data/spec/support/client_config.rb +1 -0
  89. data/spec/support/dummy_class.rb +12 -9
  90. data/spec/support/utils.rb +15 -0
  91. metadata +29 -3
@@ -58,7 +58,7 @@ describe Descope::Api::V1::Management::User do
58
58
  }
59
59
 
60
60
  it 'is expected to create a user with user data' do
61
- expect(@instance).to receive(:post).with(USER_CREATE_PATH, params)
61
+ expect(@instance).to receive(:mgmt_post).with(USER_CREATE_PATH, params)
62
62
 
63
63
  expect do
64
64
  @instance.create_user(**args)
@@ -67,7 +67,7 @@ describe Descope::Api::V1::Management::User do
67
67
 
68
68
  it 'is expected to create a test user with user data' do
69
69
  params[:test] = true
70
- expect(@instance).to receive(:post).with(TEST_USER_CREATE_PATH, params)
70
+ expect(@instance).to receive(:mgmt_post).with(TEST_USER_CREATE_PATH, params)
71
71
 
72
72
  expect do
73
73
  args[:test] = true
@@ -101,7 +101,7 @@ describe Descope::Api::V1::Management::User do
101
101
  }
102
102
  ]
103
103
  }
104
- expect(@instance).to receive(:post).with(
104
+ expect(@instance).to receive(:mgmt_post).with(
105
105
  USER_CREATE_BATCH_PATH, users_params
106
106
  )
107
107
 
@@ -117,7 +117,7 @@ describe Descope::Api::V1::Management::User do
117
117
  end
118
118
 
119
119
  it "is expected to post #{USER_CREATE_PATH} with invite true" do
120
- expect(@instance).to receive(:post).with(
120
+ expect(@instance).to receive(:mgmt_post).with(
121
121
  USER_CREATE_PATH, {
122
122
  loginId: 'name@mail.com',
123
123
  email: 'name@mail.com',
@@ -143,7 +143,7 @@ describe Descope::Api::V1::Management::User do
143
143
  end
144
144
 
145
145
  it 'is expected to respond to a user update method' do
146
- expect(@instance).to receive(:post).with(
146
+ expect(@instance).to receive(:mgmt_post).with(
147
147
  USER_UPDATE_PATH, {
148
148
  loginId: 'name@mail.com',
149
149
  email: 'name@mail.com',
@@ -171,7 +171,7 @@ describe Descope::Api::V1::Management::User do
171
171
  end
172
172
 
173
173
  it 'is expected to respond to a user delete method' do
174
- expect(@instance).to receive(:post).with(
174
+ expect(@instance).to receive(:mgmt_post).with(
175
175
  USER_DELETE_PATH, { loginId: 'name@mail.com' }
176
176
  )
177
177
 
@@ -183,7 +183,7 @@ describe Descope::Api::V1::Management::User do
183
183
 
184
184
  context '.delete_all_user' do
185
185
  it 'is expected to respond to a user delete method' do
186
- expect(@instance).to receive(:delete).with(USER_DELETE_ALL_TEST_USERS_PATH)
186
+ expect(@instance).to receive(:mgmt_delete).with(USER_DELETE_ALL_TEST_USERS_PATH)
187
187
 
188
188
  expect do
189
189
  @instance.delete_all_test_users
@@ -197,7 +197,7 @@ describe Descope::Api::V1::Management::User do
197
197
  end
198
198
 
199
199
  it "is expected to get #{USER_LOAD_PATH} with login_id" do
200
- expect(@instance).to receive(:get).with(
200
+ expect(@instance).to receive(:mgmt_get).with(
201
201
  USER_LOAD_PATH, { loginId: 'someone' }
202
202
  )
203
203
  expect { @instance.load_user('someone') }.not_to raise_error
@@ -206,7 +206,7 @@ describe Descope::Api::V1::Management::User do
206
206
 
207
207
  context '.load_by_user_id' do
208
208
  it "is expected to get #{USER_LOAD_PATH} with user_id" do
209
- allow(@instance).to receive(:get).with(
209
+ allow(@instance).to receive(:mgmt_get).with(
210
210
  USER_LOAD_PATH, { userId: 'ABCD' }
211
211
  )
212
212
  expect { @instance.load_by_user_id('ABCD') }.not_to raise_error
@@ -215,7 +215,7 @@ describe Descope::Api::V1::Management::User do
215
215
 
216
216
  context '.logout_user' do
217
217
  it 'is expected to respond to a logout user method' do
218
- expect(@instance).to receive(:post).with(
218
+ expect(@instance).to receive(:mgmt_post).with(
219
219
  USER_LOGOUT_PATH, { loginId: 'name@mail.com' }
220
220
  )
221
221
 
@@ -225,7 +225,7 @@ describe Descope::Api::V1::Management::User do
225
225
  end
226
226
 
227
227
  it 'is expected to respond to a logout user by id method' do
228
- expect(@instance).to receive(:post).with(
228
+ expect(@instance).to receive(:mgmt_post).with(
229
229
  USER_LOGOUT_PATH, { userId: 'U2ZpARjKAJJmq0fzU2lXNNCGnF4j' }
230
230
  )
231
231
 
@@ -241,7 +241,7 @@ describe Descope::Api::V1::Management::User do
241
241
 
242
242
  tenant_role_ids = { 'tenant1' => ['roleA', 'roleB'] }
243
243
  tenant_role_names = { 'tenant1' => ['roleName1', 'roleName2'] }
244
- expect(@instance).to receive(:post).with(
244
+ expect(@instance).to receive(:mgmt_post).with(
245
245
  USERS_SEARCH_PATH, {
246
246
  loginId: 'someone@example.com',
247
247
  tenantIds: [],
@@ -284,7 +284,7 @@ describe Descope::Api::V1::Management::User do
284
284
  it 'is expected to respond to a get_provider_token method' do
285
285
  expect(@instance).to respond_to(:get_provider_token)
286
286
 
287
- expect(@instance).to receive(:get).with(
287
+ expect(@instance).to receive(:mgmt_get).with(
288
288
  USER_GET_PROVIDER_TOKEN, {
289
289
  loginId: 'someone@example.com',
290
290
  provider: 'google-oauth2'
@@ -304,7 +304,7 @@ describe Descope::Api::V1::Management::User do
304
304
  it 'is expected to respond to a activate method' do
305
305
  expect(@instance).to respond_to(:activate)
306
306
 
307
- expect(@instance).to receive(:post).with(
307
+ expect(@instance).to receive(:mgmt_post).with(
308
308
  USER_UPDATE_STATUS_PATH, {
309
309
  loginId: 'someone@example.com',
310
310
  status: 'enabled'
@@ -321,7 +321,7 @@ describe Descope::Api::V1::Management::User do
321
321
  it 'is expected to respond to a activate method' do
322
322
  expect(@instance).to respond_to(:activate)
323
323
 
324
- expect(@instance).to receive(:post).with(
324
+ expect(@instance).to receive(:mgmt_post).with(
325
325
  USER_UPDATE_STATUS_PATH, {
326
326
  loginId: 'someone@example.com',
327
327
  status: 'disabled'
@@ -338,7 +338,7 @@ describe Descope::Api::V1::Management::User do
338
338
  it 'is expected to respond to a update_email method' do
339
339
  expect(@instance).to respond_to(:update_email)
340
340
 
341
- expect(@instance).to receive(:post).with(
341
+ expect(@instance).to receive(:mgmt_post).with(
342
342
  USER_UPDATE_EMAIL_PATH, {
343
343
  loginId: 'someone@example.com',
344
344
  email: 'tester@test.com',
@@ -360,7 +360,7 @@ describe Descope::Api::V1::Management::User do
360
360
  it 'is expected to respond to a update_phone method' do
361
361
  expect(@instance).to respond_to(:update_phone)
362
362
 
363
- expect(@instance).to receive(:post).with(
363
+ expect(@instance).to receive(:mgmt_post).with(
364
364
  USER_UPDATE_PHONE_PATH, {
365
365
  loginId: 'someone@example.com',
366
366
  phone: '1234567890',
@@ -382,7 +382,7 @@ describe Descope::Api::V1::Management::User do
382
382
  it 'is expected to respond to a update_display_name method' do
383
383
  expect(@instance).to respond_to(:update_display_name)
384
384
 
385
- expect(@instance).to receive(:post).with(
385
+ expect(@instance).to receive(:mgmt_post).with(
386
386
  USER_UPDATE_NAME_PATH, {
387
387
  loginId: 'someone@example.com',
388
388
  name: 'some guy',
@@ -408,7 +408,7 @@ describe Descope::Api::V1::Management::User do
408
408
  it 'is expected to respond to a update_picture method' do
409
409
  expect(@instance).to respond_to(:update_picture)
410
410
 
411
- expect(@instance).to receive(:post).with(
411
+ expect(@instance).to receive(:mgmt_post).with(
412
412
  USER_UPDATE_PICTURE_PATH, {
413
413
  loginId: 'someone@example.com',
414
414
  picture: 'https://www.example.com/picture.png'
@@ -428,7 +428,7 @@ describe Descope::Api::V1::Management::User do
428
428
  it 'is expected to respond to a update_custom_attribute method' do
429
429
  expect(@instance).to respond_to(:update_custom_attribute)
430
430
 
431
- expect(@instance).to receive(:post).with(
431
+ expect(@instance).to receive(:mgmt_post).with(
432
432
  USER_UPDATE_CUSTOM_ATTRIBUTE_PATH, {
433
433
  loginId: 'someone@example.com',
434
434
  attributeKey: 'OU',
@@ -450,7 +450,7 @@ describe Descope::Api::V1::Management::User do
450
450
  it 'is expected to respond to a add_roles method' do
451
451
  expect(@instance).to respond_to(:user_add_roles)
452
452
 
453
- expect(@instance).to receive(:post).with(
453
+ expect(@instance).to receive(:mgmt_post).with(
454
454
  USER_ADD_ROLE_PATH, {
455
455
  loginId: 'someone@example.com',
456
456
  roleNames: %w[role1 role2],
@@ -472,7 +472,7 @@ describe Descope::Api::V1::Management::User do
472
472
  it 'is expected to respond to a user_remove_roles method' do
473
473
  expect(@instance).to respond_to(:user_remove_roles)
474
474
 
475
- expect(@instance).to receive(:post).with(
475
+ expect(@instance).to receive(:mgmt_post).with(
476
476
  USER_REMOVE_ROLE_PATH, {
477
477
  loginId: 'someone@example.com',
478
478
  roleNames: %w[role1 role2],
@@ -494,7 +494,7 @@ describe Descope::Api::V1::Management::User do
494
494
  it 'is expected to respond to a add_tenant method' do
495
495
  expect(@instance).to respond_to(:user_add_tenant)
496
496
 
497
- expect(@instance).to receive(:post).with(
497
+ expect(@instance).to receive(:mgmt_post).with(
498
498
  USER_ADD_TENANT_PATH, {
499
499
  loginId: 'someone@example.com',
500
500
  tenantId: 'tenant1'
@@ -514,7 +514,7 @@ describe Descope::Api::V1::Management::User do
514
514
  it 'is expected to respond to a remove_tenant method' do
515
515
  expect(@instance).to respond_to(:user_remove_tenant)
516
516
 
517
- expect(@instance).to receive(:post).with(
517
+ expect(@instance).to receive(:mgmt_post).with(
518
518
  USER_REMOVE_TENANT_PATH, {
519
519
  loginId: 'someone@example.com',
520
520
  tenantId: 'tenant1'
@@ -534,7 +534,7 @@ describe Descope::Api::V1::Management::User do
534
534
  it 'is expected to respond to a add_tenant_role method' do
535
535
  expect(@instance).to respond_to(:add_tenant_role)
536
536
 
537
- expect(@instance).to receive(:post).with(
537
+ expect(@instance).to receive(:mgmt_post).with(
538
538
  USER_ADD_TENANT_PATH, {
539
539
  loginId: 'someone@example.com',
540
540
  tenantId: 'tenant1',
@@ -556,7 +556,7 @@ describe Descope::Api::V1::Management::User do
556
556
  it 'is expected to respond to a remove_tenant_role method' do
557
557
  expect(@instance).to respond_to(:user_remove_tenant_roles)
558
558
 
559
- expect(@instance).to receive(:post).with(
559
+ expect(@instance).to receive(:mgmt_post).with(
560
560
  USER_REMOVE_TENANT_PATH, {
561
561
  loginId: 'someone@example.com',
562
562
  tenantId: 'tenant1',
@@ -578,7 +578,7 @@ describe Descope::Api::V1::Management::User do
578
578
  it 'is expected to respond to a set_temporary_password method' do
579
579
  expect(@instance).to respond_to(:set_temporary_password)
580
580
 
581
- expect(@instance).to receive(:post).with(
581
+ expect(@instance).to receive(:mgmt_post).with(
582
582
  USER_SET_TEMPORARY_PASSWORD_PATH, {
583
583
  loginId: 'someone@example.com',
584
584
  password: 's3cr3t'
@@ -598,7 +598,7 @@ describe Descope::Api::V1::Management::User do
598
598
  it 'is expected to respond to a set_active_password method' do
599
599
  expect(@instance).to respond_to(:set_active_password)
600
600
 
601
- expect(@instance).to receive(:post).with(
601
+ expect(@instance).to receive(:mgmt_post).with(
602
602
  USER_SET_ACTIVE_PASSWORD_PATH, {
603
603
  loginId: 'someone@example.com',
604
604
  password: 's3cr3t'
@@ -618,7 +618,7 @@ describe Descope::Api::V1::Management::User do
618
618
  it 'is expected to respond to a set_password method' do
619
619
  expect(@instance).to respond_to(:set_password)
620
620
 
621
- expect(@instance).to receive(:post).with(
621
+ expect(@instance).to receive(:mgmt_post).with(
622
622
  USER_SET_PASSWORD_PATH, {
623
623
  loginId: 'someone@example.com',
624
624
  password: 's3cr3t'
@@ -638,7 +638,7 @@ describe Descope::Api::V1::Management::User do
638
638
  it 'is expected to respond to a expire_password method' do
639
639
  expect(@instance).to respond_to(:expire_password)
640
640
 
641
- expect(@instance).to receive(:post).with(
641
+ expect(@instance).to receive(:mgmt_post).with(
642
642
  USER_EXPIRE_PASSWORD_PATH, { loginId: 'someone@example.com' }
643
643
  )
644
644
 
@@ -650,7 +650,7 @@ describe Descope::Api::V1::Management::User do
650
650
  it 'is expected to respond to a generate_otp_for_test method' do
651
651
  expect(@instance).to respond_to(:generate_otp_for_test_user)
652
652
 
653
- expect(@instance).to receive(:post).with(
653
+ expect(@instance).to receive(:mgmt_post).with(
654
654
  USER_GENERATE_OTP_FOR_TEST_PATH, {
655
655
  loginId: 'someone@example.com',
656
656
  deliveryMethod: 'email'
@@ -670,7 +670,7 @@ describe Descope::Api::V1::Management::User do
670
670
  it 'is expected to respond to a generate_enchanted_link_for_test method' do
671
671
  expect(@instance).to respond_to(:generate_enchanted_link_for_test_user)
672
672
 
673
- expect(@instance).to receive(:post).with(
673
+ expect(@instance).to receive(:mgmt_post).with(
674
674
  USER_GENERATE_ENCHANTED_LINK_FOR_TEST_PATH, {
675
675
  loginId: 'someone@example.com',
676
676
  URI: 'https://www.example.com'
@@ -690,7 +690,7 @@ describe Descope::Api::V1::Management::User do
690
690
  it 'is expected to respond to a update_jwt method' do
691
691
  expect(@instance).to respond_to(:update_jwt)
692
692
 
693
- expect(@instance).to receive(:post).with(
693
+ expect(@instance).to receive(:mgmt_post).with(
694
694
  UPDATE_JWT_PATH, {
695
695
  jwt: 'eyJ3abcde12345',
696
696
  customClaims: { 'claim1' => 'value1', 'claim2' => 'value2' }
@@ -717,7 +717,7 @@ describe Descope::Api::V1::Management::User do
717
717
  customClaims: { 'abc': '123' }
718
718
  }
719
719
 
720
- expect(@instance).to receive(:post).with(
720
+ expect(@instance).to receive(:mgmt_post).with(
721
721
  USER_GENERATE_EMBEDDED_LINK_PATH,
722
722
  request_params
723
723
  )
@@ -737,7 +737,7 @@ describe Descope::Api::V1::Management::User do
737
737
  end
738
738
 
739
739
  it 'is expected to respond to a user patch method' do
740
- expect(@instance).to receive(:patch).with(
740
+ expect(@instance).to receive(:mgmt_patch).with(
741
741
  USER_PATCH_PATH, {
742
742
  loginId: 'name@mail.com',
743
743
  email: 'name@mail.com',
@@ -765,7 +765,7 @@ describe Descope::Api::V1::Management::User do
765
765
 
766
766
  tenant_role_ids = { 'tenant1' => ['roleA', 'roleB'] }
767
767
  tenant_role_names = { 'tenant1' => ['roleName1', 'roleName2'] }
768
- expect(@instance).to receive(:post).with(
768
+ expect(@instance).to receive(:mgmt_post).with(
769
769
  TEST_USERS_SEARCH_PATH, {
770
770
  tenantIds: %w[t1 t2],
771
771
  roleNames: %w[r1 r2],
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ # Covers the Authorization header the SDK actually sends. The auth management key rides along with
6
+ # every authentication request so that methods whose public access has been disabled can still be
7
+ # used, and it is never sent on management requests - those carry the management key instead.
8
+ describe 'auth management key' do
9
+ let(:project_id) { 'P2abcde12345' }
10
+ let(:management_key) { 'mgmt-key' }
11
+ let(:auth_management_key) { 'auth-key' }
12
+ let(:refresh_token) { 'refresh-token' }
13
+
14
+ # Stubs the RestClient boundary on both of the client's HTTP clients and hands back the
15
+ # Authorization header of whichever one ends up being used.
16
+ def authorization_for(client)
17
+ captured = nil
18
+ [client.auth_http, client.mgmt_http].each do |http|
19
+ allow(http).to receive(:call) do |_method, _url, _timeout, headers, _body|
20
+ captured = headers['Authorization']
21
+ double('response', code: 200, body: '{}', cookies: {}, headers: {})
22
+ end
23
+ end
24
+ yield
25
+ captured
26
+ end
27
+
28
+ def build_client(**options)
29
+ Descope::Client.new({ project_id: project_id, log_level: 'fatal' }.merge(options))
30
+ end
31
+
32
+ # An authentication request that presents no token of its own.
33
+ def sign_up(client)
34
+ client.otp_sign_up(method: Descope::Mixins::Common::DeliveryMethod::EMAIL, login_id: 'someone@example.com')
35
+ end
36
+
37
+ around do |example|
38
+ keys = %w[DESCOPE_MANAGEMENT_KEY DESCOPE_AUTH_MANAGEMENT_KEY]
39
+ saved = keys.to_h { |var| [var, ENV[var]] }
40
+ keys.each { |var| ENV.delete(var) }
41
+ example.run
42
+ ensure
43
+ saved.each { |var, value| ENV[var] = value }
44
+ end
45
+
46
+ context 'on authentication requests' do
47
+ it 'sends only the project ID when no auth management key is configured' do
48
+ client = build_client
49
+ header = authorization_for(client) { sign_up(client) }
50
+ expect(header).to eq("Bearer #{project_id}")
51
+ end
52
+
53
+ it 'appends the auth management key when one is configured' do
54
+ client = build_client(auth_management_key: auth_management_key)
55
+ header = authorization_for(client) { sign_up(client) }
56
+ expect(header).to eq("Bearer #{project_id}:#{auth_management_key}")
57
+ end
58
+
59
+ it 'appends the auth management key after a refresh token' do
60
+ client = build_client(auth_management_key: auth_management_key)
61
+ header = authorization_for(client) { client.me(refresh_token) }
62
+ expect(header).to eq("Bearer #{project_id}:#{refresh_token}:#{auth_management_key}")
63
+ end
64
+
65
+ it 'never sends the management key' do
66
+ client = build_client(management_key: management_key, auth_management_key: auth_management_key)
67
+ header = authorization_for(client) { sign_up(client) }
68
+ expect(header).to eq("Bearer #{project_id}:#{auth_management_key}")
69
+ expect(header).to_not include(management_key)
70
+ end
71
+
72
+ # DummyClass swallows extra_headers, so only a real client exercises this argument.
73
+ it 'tolerates a nil extra_headers from the caller' do
74
+ client = build_client(auth_management_key: auth_management_key)
75
+ header = authorization_for(client) { client.post(SIGN_IN_PASSWORD_PATH, {}, nil, refresh_token) }
76
+ expect(header).to eq("Bearer #{project_id}:#{refresh_token}:#{auth_management_key}")
77
+ end
78
+
79
+ it 'signs in with an enchanted link' do
80
+ client = build_client(auth_management_key: auth_management_key)
81
+ header = authorization_for(client) do
82
+ client.enchanted_link_sign_in(login_id: 'someone@example.com', uri: 'https://example.com')
83
+ end
84
+ expect(header).to eq("Bearer #{project_id}:#{auth_management_key}")
85
+ end
86
+
87
+ it 'does not substitute a key for an empty refresh token' do
88
+ client = build_client(management_key: management_key, auth_management_key: auth_management_key)
89
+ header = authorization_for(client) { client.sign_out('') }
90
+ expect(header).to eq("Bearer #{project_id}:#{auth_management_key}")
91
+ end
92
+ end
93
+
94
+ context 'on management requests' do
95
+ it 'appends the management key' do
96
+ client = build_client(management_key: management_key)
97
+ header = authorization_for(client) { client.load_all_tenants }
98
+ expect(header).to eq("Bearer #{project_id}:#{management_key}")
99
+ end
100
+
101
+ it 'never sends the auth management key' do
102
+ client = build_client(management_key: management_key, auth_management_key: auth_management_key)
103
+ header = authorization_for(client) { client.load_all_tenants }
104
+ expect(header).to eq("Bearer #{project_id}:#{management_key}")
105
+ expect(header).to_not include(auth_management_key)
106
+ end
107
+
108
+ it 'sends only the project ID when no management key is configured' do
109
+ client = build_client(auth_management_key: auth_management_key)
110
+ header = authorization_for(client) { client.load_all_tenants }
111
+ expect(header).to eq("Bearer #{project_id}")
112
+ end
113
+ end
114
+
115
+ context 'configuration' do
116
+ it 'falls back to the DESCOPE_AUTH_MANAGEMENT_KEY env var' do
117
+ ENV['DESCOPE_AUTH_MANAGEMENT_KEY'] = 'env-auth-key'
118
+ client = build_client
119
+ header = authorization_for(client) { sign_up(client) }
120
+ expect(header).to eq("Bearer #{project_id}:env-auth-key")
121
+ end
122
+
123
+ it 'prefers the configured key over the env var' do
124
+ ENV['DESCOPE_AUTH_MANAGEMENT_KEY'] = 'env-auth-key'
125
+ client = build_client(auth_management_key: auth_management_key)
126
+ header = authorization_for(client) { sign_up(client) }
127
+ expect(header).to eq("Bearer #{project_id}:#{auth_management_key}")
128
+ end
129
+
130
+ it 'keeps the two keys distinct' do
131
+ ENV['DESCOPE_MANAGEMENT_KEY'] = management_key
132
+ ENV['DESCOPE_AUTH_MANAGEMENT_KEY'] = auth_management_key
133
+ client = build_client
134
+ expect(client.auth_http.authorization_header).to eq(
135
+ { 'Authorization' => "Bearer #{project_id}:#{auth_management_key}" }
136
+ )
137
+ expect(client.mgmt_http.authorization_header).to eq(
138
+ { 'Authorization' => "Bearer #{project_id}:#{management_key}" }
139
+ )
140
+ end
141
+ end
142
+
143
+ describe 'debug logging' do
144
+ it 'masks everything after the project ID' do
145
+ client = build_client(auth_management_key: auth_management_key)
146
+ masked = client.auth_http.mask_authorization(client.auth_http.authorization_header)
147
+ expect(masked['Authorization']).to eq("Bearer #{project_id}:***")
148
+ expect(masked['Authorization']).to_not include(auth_management_key)
149
+ end
150
+ end
151
+ end
@@ -94,6 +94,17 @@ describe Descope::Mixins::HTTP do
94
94
  expect(result['cookies']['DSR']).to eq('refresh_jwt_token')
95
95
  end
96
96
 
97
+ it 'parses cookies from Set-Cookie headers with symbol key (real RestClient behavior)' do
98
+ # RestClient returns headers with symbol keys like :set_cookie, not string keys
99
+ mock_headers = { set_cookie: set_cookie_headers }
100
+
101
+ result = @instance.safe_parse_json(mock_body, cookies: {}, headers: mock_headers)
102
+
103
+ expect(result['cookies']).to_not be_nil
104
+ expect(result['cookies']['DS']).to eq('session_jwt_token')
105
+ expect(result['cookies']['DSR']).to eq('refresh_jwt_token')
106
+ end
107
+
97
108
  it 'parses cookies from Set-Cookie header when headers is a string' do
98
109
  mock_headers = { 'Set-Cookie' => set_cookie_headers.first }
99
110
 
@@ -120,7 +131,7 @@ describe Descope::Mixins::HTTP do
120
131
  "DS=#{jwt_session}; Path=/; Domain=custom.example.com; HttpOnly; Secure; SameSite=None",
121
132
  "DSR=#{jwt_refresh}; Path=/; Domain=custom.example.com; HttpOnly; Secure; SameSite=None; Max-Age=2592000"
122
133
  ]
123
- mock_headers = { 'set-cookie' => complex_headers }
134
+ mock_headers = { set_cookie: complex_headers }
124
135
 
125
136
  result = @instance.safe_parse_json(mock_body, cookies: {}, headers: mock_headers)
126
137
 
@@ -134,7 +145,7 @@ describe Descope::Mixins::HTTP do
134
145
  'CLOUDFLARE_SESSION=cf_token; Path=/; Domain=.example.com; HttpOnly',
135
146
  'DSR=refresh_token; Path=/; Domain=dev.example.com; HttpOnly'
136
147
  ]
137
- mock_headers = { 'set-cookie' => mixed_headers }
148
+ mock_headers = { set_cookie: mixed_headers }
138
149
 
139
150
  result = @instance.safe_parse_json(mock_body, cookies: {}, headers: mock_headers)
140
151
 
@@ -175,7 +186,7 @@ describe Descope::Mixins::HTTP do
175
186
  'DS=; Path=/; Domain=example.com', # Empty value
176
187
  '=value_without_name; Path=/', # No name
177
188
  ]
178
- mock_headers = { 'set-cookie' => malformed_headers }
189
+ mock_headers = { set_cookie: malformed_headers }
179
190
 
180
191
  result = @instance.safe_parse_json(mock_body, cookies: {}, headers: mock_headers)
181
192
 
@@ -186,7 +197,7 @@ describe Descope::Mixins::HTTP do
186
197
  it 'prefers RestClient cookies over Set-Cookie headers when both available' do
187
198
  mock_cookies = { 'DS' => 'restclient_token' }
188
199
  set_cookie_headers = ['DS=header_token; Path=/; Domain=example.com']
189
- mock_headers = { 'set-cookie' => set_cookie_headers }
200
+ mock_headers = { set_cookie: set_cookie_headers }
190
201
 
191
202
  result = @instance.safe_parse_json(mock_body, cookies: mock_cookies, headers: mock_headers)
192
203
 
@@ -204,7 +215,7 @@ describe Descope::Mixins::HTTP do
204
215
  allow(mock_response).to receive(:body).and_return('{"success": true}')
205
216
  allow(mock_response).to receive(:cookies).and_return({})
206
217
  allow(mock_response).to receive(:headers).and_return({
207
- 'set-cookie' => ['DS=test_token; Domain=custom.example.com']
218
+ set_cookie: ['DS=test_token; Domain=custom.example.com']
208
219
  })
209
220
 
210
221
  allow(@instance).to receive(:call).and_return(mock_response)
@@ -215,4 +226,4 @@ describe Descope::Mixins::HTTP do
215
226
  expect(result['cookies']['DS']).to eq('test_token')
216
227
  end
217
228
  end
218
- end
229
+ end
@@ -11,6 +11,7 @@ module Configuration
11
11
  descope_base_uri: ENV.fetch('DESCOPE_BASE_URI', Descope::Mixins::Common::DEFAULT_BASE_URL),
12
12
  project_id: ENV.fetch('DESCOPE_PROJECT_ID', nil),
13
13
  management_key: ENV.fetch('DESCOPE_MANAGEMENT_KEY', nil),
14
+ auth_management_key: ENV.fetch('DESCOPE_AUTH_MANAGEMENT_KEY', nil),
14
15
  log_level: ENV.fetch('DESCOPE_LOG_LEVEL', 'info')
15
16
  }
16
17
  end
@@ -33,18 +33,21 @@ class DummyClass
33
33
  @headers.merge!(h.to_hash)
34
34
  end
35
35
 
36
+ # Mirrors Descope::HttpClient#authorization_header. Specs that need to assert on the real bearer
37
+ # should drive a Descope::Client instead - see spec/lib.descope/auth_management_key_spec.rb.
36
38
  def authorization_header(pswd = nil)
37
- pswd = @default_pswd if pswd.nil? || pswd.empty?
38
- bearer = "#{@project_id}:#{pswd}"
39
- add_headers('Authorization' => "Bearer #{bearer}")
39
+ bearer = [@project_id, pswd, @key].reject { |part| part.nil? || part.to_s.empty? }.join(':')
40
+ { 'Authorization' => "Bearer #{bearer}" }
40
41
  end
41
42
 
42
- %i[get post post_file post_form put patch delete delete_with_body].each do |method|
43
- define_method(method) do |uri, body = {}, extra_headers = {}, pswd = nil|
44
- body = body.delete_if { |_, v| v.nil? }
45
- authorization_header(pswd) unless pswd.nil? || pswd.empty?
46
- {}
43
+ # The bare verbs stand in for authentication requests, the mgmt_ prefixed ones for management
44
+ # requests, matching how Descope::Client routes them to its two HTTP clients.
45
+ Descope::Mixins::HTTP::HTTP_METHODS.each do |method|
46
+ [method, :"mgmt_#{method}"].each do |name|
47
+ define_method(name) do |_uri, _body = {}, _extra_headers = {}, pswd = nil|
48
+ add_headers(authorization_header(pswd)) unless pswd.nil? || pswd.empty?
49
+ {}
50
+ end
47
51
  end
48
52
  end
49
-
50
53
  end
@@ -35,4 +35,19 @@ module SpecUtils
35
35
  prefix = ENV['GITHUB_RUN_NUMBER'] || ENV['GITHUB_RUN_ID']
36
36
  prefix ? "build#{prefix}-" : "local-#{Time.now.to_i}-"
37
37
  end
38
+
39
+ def wait_for_condition(max_wait: 60, interval: 2, description: 'condition')
40
+ start = Time.now
41
+ loop do
42
+ result = yield
43
+ return result if result
44
+
45
+ elapsed = Time.now - start
46
+ if elapsed > max_wait
47
+ raise "Timeout after #{max_wait}s waiting for #{description}"
48
+ end
49
+
50
+ sleep interval
51
+ end
52
+ end
38
53
  end