descope 1.0.5 → 1.0.7

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +2 -2
  3. data/.github/workflows/publish-gem.yaml +39 -7
  4. data/.gitignore +2 -0
  5. data/.ruby-version +1 -1
  6. data/Gemfile +7 -7
  7. data/Gemfile.lock +70 -65
  8. data/README.md +175 -52
  9. data/descope.gemspec +25 -20
  10. data/examples/ruby/.ruby-version +1 -0
  11. data/examples/ruby/access_key_app.rb +4 -3
  12. data/examples/ruby/enchantedlink_app.rb +1 -0
  13. data/examples/ruby/magiclink_app.rb +1 -0
  14. data/examples/ruby/management/.ruby-version +1 -0
  15. data/examples/ruby/management/Gemfile +2 -2
  16. data/examples/ruby/management/access_key_app.rb +2 -0
  17. data/examples/ruby/management/audit_app.rb +32 -8
  18. data/examples/ruby/management/authz_app.rb +1 -0
  19. data/examples/ruby/management/flow_app.rb +1 -0
  20. data/examples/ruby/management/permission_app.rb +3 -2
  21. data/examples/ruby/management/role_app.rb +3 -2
  22. data/examples/ruby/management/tenant_app.rb +1 -0
  23. data/examples/ruby/management/user_app.rb +1 -0
  24. data/examples/ruby/oauth_app.rb +1 -0
  25. data/examples/ruby/otp_app.rb +38 -12
  26. data/examples/ruby/password_app.rb +8 -7
  27. data/examples/ruby/saml_app.rb +1 -0
  28. data/examples/ruby/version_check.rb +17 -0
  29. data/examples/ruby-on-rails-api/descope/Gemfile +9 -7
  30. data/examples/ruby-on-rails-api/descope/Gemfile.lock +121 -90
  31. data/examples/ruby-on-rails-api/descope/README.md +18 -18
  32. data/examples/ruby-on-rails-api/descope/app/assets/builds/application.css +20092 -23
  33. data/examples/ruby-on-rails-api/descope/app/assets/builds/application.js +0 -1
  34. data/examples/ruby-on-rails-api/descope/app/assets/builds/components/index.js +0 -14
  35. data/examples/ruby-on-rails-api/descope/package-lock.json +1073 -19302
  36. data/examples/ruby-on-rails-api/descope/package.json +8 -16
  37. data/examples/ruby-on-rails-api/descope/yarn.lock +557 -10641
  38. data/lib/descope/api/v1/auth/enchantedlink.rb +3 -1
  39. data/lib/descope/api/v1/auth/magiclink.rb +3 -1
  40. data/lib/descope/api/v1/auth/otp.rb +24 -15
  41. data/lib/descope/api/v1/auth/password.rb +6 -2
  42. data/lib/descope/api/v1/auth/totp.rb +3 -1
  43. data/lib/descope/api/v1/auth.rb +64 -32
  44. data/lib/descope/api/v1/management/audit.rb +24 -0
  45. data/lib/descope/api/v1/management/common.rb +21 -5
  46. data/lib/descope/api/v1/management/sso_application.rb +236 -0
  47. data/lib/descope/api/v1/management/sso_settings.rb +2 -24
  48. data/lib/descope/api/v1/management/user.rb +151 -13
  49. data/lib/descope/api/v1/management.rb +2 -0
  50. data/lib/descope/api/v1/session.rb +37 -4
  51. data/lib/descope/mixins/common.rb +6 -2
  52. data/lib/descope/mixins/http.rb +60 -9
  53. data/lib/descope/mixins/initializer.rb +2 -1
  54. data/lib/descope/mixins/logging.rb +12 -4
  55. data/lib/descope/mixins/validation.rb +21 -6
  56. data/lib/descope/version.rb +1 -1
  57. data/spec/descope/api/v1/auth_spec.rb +29 -0
  58. data/spec/descope/api/v1/auth_token_extraction_spec.rb +126 -0
  59. data/spec/descope/api/v1/session_refresh_spec.rb +98 -0
  60. data/spec/factories/user.rb +1 -1
  61. data/spec/integration/lib.descope/api/v1/auth/enchantedlink_spec.rb +1 -1
  62. data/spec/integration/lib.descope/api/v1/auth/magiclink_spec.rb +1 -1
  63. data/spec/integration/lib.descope/api/v1/auth/otp_spec.rb +73 -8
  64. data/spec/integration/lib.descope/api/v1/auth/session_spec.rb +49 -0
  65. data/spec/integration/lib.descope/api/v1/auth/totp_spec.rb +1 -1
  66. data/spec/integration/lib.descope/api/v1/management/access_key_spec.rb +3 -0
  67. data/spec/integration/lib.descope/api/v1/management/audit_spec.rb +38 -0
  68. data/spec/integration/lib.descope/api/v1/management/authz_spec.rb +2 -0
  69. data/spec/integration/lib.descope/api/v1/management/flow_spec.rb +3 -1
  70. data/spec/integration/lib.descope/api/v1/management/permissions_spec.rb +4 -2
  71. data/spec/integration/lib.descope/api/v1/management/project_spec.rb +2 -0
  72. data/spec/integration/lib.descope/api/v1/management/roles_spec.rb +3 -1
  73. data/spec/integration/lib.descope/api/v1/management/user_spec.rb +55 -6
  74. data/spec/lib.descope/api/v1/auth/enchantedlink_spec.rb +11 -2
  75. data/spec/lib.descope/api/v1/auth/otp_spec.rb +176 -18
  76. data/spec/lib.descope/api/v1/auth/password_spec.rb +10 -1
  77. data/spec/lib.descope/api/v1/auth_spec.rb +168 -6
  78. data/spec/lib.descope/api/v1/cookie_domain_fix_integration_spec.rb +245 -0
  79. data/spec/lib.descope/api/v1/management/audit_spec.rb +92 -0
  80. data/spec/lib.descope/api/v1/management/sso_application_spec.rb +217 -0
  81. data/spec/lib.descope/api/v1/management/sso_settings_spec.rb +2 -2
  82. data/spec/lib.descope/api/v1/management/user_spec.rb +134 -46
  83. data/spec/lib.descope/api/v1/session_spec.rb +119 -6
  84. data/spec/lib.descope/mixins/http_spec.rb +218 -0
  85. data/spec/support/client_config.rb +0 -1
  86. data/spec/support/utils.rb +6 -0
  87. metadata +34 -137
  88. data/examples/ruby-on-rails-api/descope/app/assets/builds/reportWebVitals.js +0 -211
  89. data/examples/ruby-on-rails-api/descope/app/assets/builds/reportWebVitals.js.map +0 -7
@@ -181,7 +181,7 @@ describe Descope::Api::V1::Auth do
181
181
 
182
182
  expect do
183
183
  exp_in_seconds = 20
184
- puts "Sleeping for #{exp_in_seconds} seconds to test token expiration. Please wait..."
184
+ puts "\nAuthSpec.validate_token::Sleeping for #{exp_in_seconds} seconds to test token expiration. Please wait...\n"
185
185
  sleep(exp_in_seconds)
186
186
  @instance.send(:validate_token, token)
187
187
  end.to raise_error(
@@ -249,7 +249,13 @@ describe Descope::Api::V1::Auth do
249
249
  end
250
250
 
251
251
  it 'is expected to select tenant' do
252
- jwt_response = { 'fake': 'response' }
252
+ jwt_response = {
253
+ 'sessionJwt' => 'fake_session_jwt',
254
+ 'refreshJwt' => 'fake_refresh_jwt',
255
+ 'cookies' => {
256
+ 'refresh_token' => 'fake_refresh_cookie'
257
+ }
258
+ }
253
259
 
254
260
  expect(@instance).to receive(:post).with(
255
261
  SELECT_TENANT_PATH, { tenantId: 'tenant123' }, {}, 'refresh-token'
@@ -390,20 +396,26 @@ describe Descope::Api::V1::Auth do
390
396
  end
391
397
 
392
398
  it 'is expected to successfully exchange access key without login_options' do
393
- jwt_response = { 'fake': 'response' }
399
+ jwt_response = {
400
+ 'sessionJwt' => 'fake_session_jwt',
401
+ 'refreshJwt' => 'fake_refresh_jwt'
402
+ }
394
403
  access_key = 'abc'
395
404
 
396
405
  expect(@instance).to receive(:post).with(
397
406
  EXCHANGE_AUTH_ACCESS_KEY_PATH, { loginOptions: {}, audience: 'IT' }, {}, access_key
398
407
  ).and_return(jwt_response)
399
408
 
400
- allow(@instance).to receive(:generate_jwt_response).and_return(jwt_response)
409
+ allow(@instance).to receive(:generate_auth_info).and_return(jwt_response)
401
410
 
402
411
  expect { @instance.exchange_access_key(access_key:, audience: 'IT') }.not_to raise_error
403
412
  end
404
413
 
405
414
  it 'is expected to successfully exchange access key with login_options' do
406
- jwt_response = { 'fake': 'response' }
415
+ jwt_response = {
416
+ 'sessionJwt' => 'fake_session_jwt',
417
+ 'refreshJwt' => 'fake_refresh_jwt'
418
+ }
407
419
  access_key = 'abc'
408
420
 
409
421
  expect(@instance).to receive(:post).with(
@@ -413,9 +425,159 @@ describe Descope::Api::V1::Auth do
413
425
  access_key
414
426
  ).and_return(jwt_response)
415
427
 
416
- allow(@instance).to receive(:generate_jwt_response).and_return(jwt_response)
428
+ allow(@instance).to receive(:generate_auth_info).and_return(jwt_response)
417
429
 
418
430
  expect { @instance.exchange_access_key(access_key:, login_options: { customClaims: { k1: 'v1' } }, audience: 'IT') }.not_to raise_error
419
431
  end
420
432
  end
433
+
434
+ describe '#generate_auth_info cookie handling enhancements' do
435
+ let(:audience) { nil }
436
+ let(:session_jwt) { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5kZXNjb3BlLmNvbSJ9.session_sig' }
437
+ let(:refresh_jwt) { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5kZXNjb3BlLmNvbSJ9.refresh_sig' }
438
+
439
+ let(:mock_token_validation) do
440
+ {
441
+ 'iss' => 'https://api.descope.com/P2abcde12345',
442
+ 'sub' => 'U2abcde12345',
443
+ 'permissions' => ['read', 'write'],
444
+ 'roles' => ['admin'],
445
+ 'tenants' => { 'tenant1' => { 'permissions' => ['read'] } }
446
+ }
447
+ end
448
+
449
+ before do
450
+ allow(@instance).to receive(:validate_token).and_return(mock_token_validation)
451
+ end
452
+
453
+ context 'when session token is in cookies (custom domain scenario)' do
454
+ let(:response_body) do
455
+ {
456
+ 'userId' => 'test123',
457
+ 'cookieExpiration' => 1640704758,
458
+ 'cookieDomain' => 'dev.lulukuku.com',
459
+ 'cookies' => {
460
+ 'DS' => session_jwt, # Session token in cookies
461
+ 'DSR' => refresh_jwt # Refresh token in cookies
462
+ }
463
+ }
464
+ end
465
+
466
+ it 'extracts session token from cookies when not in response body' do
467
+ result = @instance.send(:generate_auth_info, response_body, nil, true, audience)
468
+
469
+ expect(result['sessionToken']).to eq(mock_token_validation)
470
+ expect(result['refreshSessionToken']).to eq(mock_token_validation)
471
+ end
472
+
473
+ it 'validates session token from cookies' do
474
+ expect(@instance).to receive(:validate_token).with(session_jwt, audience).and_return(mock_token_validation)
475
+ expect(@instance).to receive(:validate_token).with(refresh_jwt, audience).and_return(mock_token_validation)
476
+
477
+ @instance.send(:generate_auth_info, response_body, nil, true, audience)
478
+ end
479
+
480
+ it 'includes permissions and roles from cookie tokens' do
481
+ result = @instance.send(:generate_auth_info, response_body, nil, true, audience)
482
+
483
+ expect(result['permissions']).to eq(['read', 'write'])
484
+ expect(result['roles']).to eq(['admin'])
485
+ expect(result['tenants']).to eq({ 'tenant1' => { 'permissions' => ['read'] } })
486
+ end
487
+ end
488
+
489
+ context 'when session token is in response body and refresh token in cookies' do
490
+ let(:response_body) do
491
+ {
492
+ 'sessionJwt' => session_jwt, # Session token in response body
493
+ 'userId' => 'test123',
494
+ 'cookies' => {
495
+ 'DSR' => refresh_jwt # Only refresh token in cookies
496
+ }
497
+ }
498
+ end
499
+
500
+ it 'uses session token from response body and refresh token from cookies' do
501
+ expect(@instance).to receive(:validate_token).with(session_jwt, audience).and_return(mock_token_validation)
502
+ expect(@instance).to receive(:validate_token).with(refresh_jwt, audience).and_return(mock_token_validation)
503
+
504
+ result = @instance.send(:generate_auth_info, response_body, nil, true, audience)
505
+
506
+ expect(result['sessionToken']).to eq(mock_token_validation)
507
+ expect(result['refreshSessionToken']).to eq(mock_token_validation)
508
+ end
509
+ end
510
+
511
+ context 'when refresh token is passed as parameter' do
512
+ let(:response_body) do
513
+ {
514
+ 'userId' => 'test123',
515
+ 'cookies' => {
516
+ 'DS' => session_jwt # Only session token in cookies
517
+ }
518
+ }
519
+ end
520
+
521
+ it 'uses passed refresh token when not in response body or cookies' do
522
+ expect(@instance).to receive(:validate_token).with(session_jwt, audience).and_return(mock_token_validation)
523
+ expect(@instance).to receive(:validate_token).with(refresh_jwt, audience).and_return(mock_token_validation)
524
+
525
+ result = @instance.send(:generate_auth_info, response_body, refresh_jwt, true, audience)
526
+
527
+ expect(result['sessionToken']).to eq(mock_token_validation)
528
+ expect(result['refreshSessionToken']).to eq(mock_token_validation)
529
+ end
530
+ end
531
+
532
+ context 'error handling for missing tokens' do
533
+ let(:response_body) do
534
+ {
535
+ 'userId' => 'test123',
536
+ 'cookieExpiration' => 1640704758,
537
+ 'cookies' => {} # No tokens anywhere
538
+ }
539
+ end
540
+
541
+ it 'raises helpful error when no refresh token is found' do
542
+ expect {
543
+ @instance.send(:generate_auth_info, response_body, nil, true, audience)
544
+ }.to raise_error(Descope::AuthException, /Could not find refreshJwt in response body \/ cookies \/ passed in refresh_token/)
545
+ end
546
+ end
547
+
548
+ context 'backward compatibility' do
549
+ let(:traditional_response_body) do
550
+ {
551
+ 'sessionJwt' => session_jwt,
552
+ 'refreshJwt' => refresh_jwt,
553
+ 'userId' => 'test123'
554
+ }
555
+ end
556
+
557
+ it 'continues to work with traditional response body tokens' do
558
+ expect(@instance).to receive(:validate_token).with(session_jwt, audience).and_return(mock_token_validation)
559
+ expect(@instance).to receive(:validate_token).with(refresh_jwt, audience).and_return(mock_token_validation)
560
+
561
+ result = @instance.send(:generate_auth_info, traditional_response_body, nil, true, audience)
562
+
563
+ expect(result['sessionToken']).to eq(mock_token_validation)
564
+ expect(result['refreshSessionToken']).to eq(mock_token_validation)
565
+ end
566
+
567
+ it 'works with same-domain cookies (existing RestClient behavior)' do
568
+ response_with_restclient_cookies = {
569
+ 'userId' => 'test123',
570
+ 'cookies' => {
571
+ 'DSR' => refresh_jwt
572
+ }
573
+ }
574
+
575
+ expect(@instance).to receive(:validate_token).with(refresh_jwt, audience).and_return(mock_token_validation)
576
+
577
+ result = @instance.send(:generate_auth_info, response_with_restclient_cookies, nil, false, audience)
578
+
579
+ expect(result['refreshSessionToken']).to eq(mock_token_validation)
580
+ end
581
+ end
582
+ end
421
583
  end
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'Cookie Domain Fix Integration' do
6
+ before(:all) do
7
+ dummy_instance = DummyClass.new
8
+ dummy_instance.extend(Descope::Api::V1::Session)
9
+ dummy_instance.extend(Descope::Api::V1::Auth)
10
+ dummy_instance.extend(Descope::Mixins::HTTP)
11
+ dummy_instance.extend(Descope::Mixins::Common::EndpointsV1)
12
+ @instance = dummy_instance
13
+ end
14
+
15
+ describe 'refresh_session with custom domain cookies' do
16
+ let(:refresh_token) { 'test_refresh_token' }
17
+ let(:audience) { nil }
18
+
19
+ let(:session_jwt) { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5kZXNjb3BlLmNvbS9QMmFiY2RlMTIzNDUiLCJzdWIiOiJVMmFiY2RlMTIzNDUifQ.session_signature' }
20
+ let(:refresh_jwt) { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5kZXNjb3BlLmNvbS9QMmFiY2RlMTIzNDUiLCJzdWIiOiJVMmFiY2RlMTIzNDUifQ.refresh_signature' }
21
+
22
+ context 'when Descope is configured for cookie-only tokens with custom domain' do
23
+ let(:api_response_body) do
24
+ # Response body without sessionJwt/refreshJwt (cookie-only configuration)
25
+ {
26
+ 'userId' => 'test123',
27
+ 'cookieExpiration' => 1640704758,
28
+ 'cookieDomain' => 'dev.lulukuku.com',
29
+ 'cookiePath' => '/'
30
+ }
31
+ end
32
+
33
+ let(:set_cookie_headers) do
34
+ [
35
+ "DS=#{session_jwt}; Path=/; Domain=dev.lulukuku.com; HttpOnly; Secure; SameSite=None",
36
+ "DSR=#{refresh_jwt}; Path=/; Domain=dev.lulukuku.com; HttpOnly; Secure; SameSite=None; Max-Age=2592000"
37
+ ]
38
+ end
39
+
40
+ let(:mock_response) do
41
+ double('response').tap do |response|
42
+ allow(response).to receive(:code).and_return(200)
43
+ allow(response).to receive(:body).and_return(api_response_body.to_json)
44
+ allow(response).to receive(:cookies).and_return({}) # RestClient filters out custom domain cookies
45
+ allow(response).to receive(:headers).and_return({ 'set-cookie' => set_cookie_headers })
46
+ end
47
+ end
48
+
49
+ before do
50
+ allow(@instance).to receive(:validate_refresh_token_not_nil).and_return(true)
51
+ allow(@instance).to receive(:validate_token).and_return({
52
+ 'iss' => 'https://api.descope.com/P2abcde12345',
53
+ 'sub' => 'U2abcde12345',
54
+ 'permissions' => [],
55
+ 'roles' => [],
56
+ 'tenants' => {}
57
+ })
58
+ allow(@instance).to receive(:call).and_return(mock_response)
59
+ end
60
+
61
+ it 'successfully extracts tokens from Set-Cookie headers' do
62
+ result = @instance.refresh_session(refresh_token: refresh_token, audience: audience)
63
+ expect(result).to be_a(Hash)
64
+ expect(result['sessionToken']).to be_a(Hash)
65
+ expect(result['sessionToken']['iss']).to eq('https://api.descope.com/P2abcde12345')
66
+ expect(result['sessionToken']['sub']).to eq('U2abcde12345')
67
+
68
+ expect(result['refreshSessionToken']).to be_a(Hash)
69
+ expect(result['refreshSessionToken']['iss']).to eq('https://api.descope.com/P2abcde12345')
70
+ expect(result['refreshSessionToken']['sub']).to eq('U2abcde12345')
71
+
72
+ expect(result['cookieData'][:domain]).to eq('dev.lulukuku.com')
73
+ end
74
+
75
+ it 'validates the extracted session token' do
76
+ expect(@instance).to receive(:validate_token).with(session_jwt, audience).and_return({
77
+ 'iss' => 'https://api.descope.com/P2abcde12345',
78
+ 'sub' => 'U2abcde12345'
79
+ })
80
+
81
+ @instance.refresh_session(refresh_token: refresh_token, audience: audience)
82
+ end
83
+
84
+ it 'validates the extracted refresh token' do
85
+ expect(@instance).to receive(:validate_token).with(refresh_jwt, audience).and_return({
86
+ 'iss' => 'https://api.descope.com/P2abcde12345',
87
+ 'sub' => 'U2abcde12345'
88
+ })
89
+
90
+ @instance.refresh_session(refresh_token: refresh_token, audience: audience)
91
+ end
92
+
93
+ it 'includes cookie metadata in response' do
94
+ result = @instance.refresh_session(refresh_token: refresh_token, audience: audience)
95
+ expect(result['cookieData'][:domain]).to eq('dev.lulukuku.com')
96
+ end
97
+ end
98
+
99
+ context 'when only refresh token is in cookies (partial custom domain)' do
100
+ let(:api_response_body) do
101
+ {
102
+ 'sessionJwt' => session_jwt, # Session token in response body
103
+ 'userId' => 'test123',
104
+ 'cookieExpiration' => 1640704758,
105
+ 'cookieDomain' => 'dev.lulukuku.com'
106
+ }
107
+ end
108
+
109
+ let(:set_cookie_headers) do
110
+ [
111
+ "DSR=#{refresh_jwt}; Path=/; Domain=dev.lulukuku.com; HttpOnly; Secure; Max-Age=2592000"
112
+ ]
113
+ end
114
+
115
+ let(:mock_response) do
116
+ double('response').tap do |response|
117
+ allow(response).to receive(:code).and_return(200)
118
+ allow(response).to receive(:body).and_return(api_response_body.to_json)
119
+ allow(response).to receive(:cookies).and_return({})
120
+ allow(response).to receive(:headers).and_return({ 'set-cookie' => set_cookie_headers })
121
+ end
122
+ end
123
+
124
+ before do
125
+ allow(@instance).to receive(:validate_refresh_token_not_nil).and_return(true)
126
+ allow(@instance).to receive(:validate_token).and_return({
127
+ 'iss' => 'https://api.descope.com/P2abcde12345',
128
+ 'sub' => 'U2abcde12345',
129
+ 'permissions' => [],
130
+ 'roles' => [],
131
+ 'tenants' => {}
132
+ })
133
+ allow(@instance).to receive(:call).and_return(mock_response)
134
+ end
135
+
136
+ it 'handles mixed token sources (response body + custom domain cookies)' do
137
+ result = @instance.refresh_session(refresh_token: refresh_token, audience: audience)
138
+
139
+ expect(result).to be_a(Hash)
140
+ expect(result['sessionToken']).to_not be_nil
141
+ expect(result['refreshSessionToken']).to_not be_nil
142
+ end
143
+ end
144
+
145
+ context 'error handling for custom domain configurations' do
146
+ let(:api_response_body) do
147
+ {
148
+ 'userId' => 'test123',
149
+ 'cookieExpiration' => 1640704758,
150
+ 'cookieDomain' => 'dev.lulukuku.com'
151
+ }
152
+ end
153
+
154
+ let(:mock_response_no_cookies) do
155
+ double('response').tap do |response|
156
+ allow(response).to receive(:code).and_return(200)
157
+ allow(response).to receive(:body).and_return(api_response_body.to_json)
158
+ allow(response).to receive(:cookies).and_return({})
159
+ allow(response).to receive(:headers).and_return({}) # No Set-Cookie headers
160
+ end
161
+ end
162
+
163
+ before do
164
+ allow(@instance).to receive(:validate_refresh_token_not_nil).and_return(true)
165
+ allow(@instance).to receive(:validate_token).and_return({
166
+ 'iss' => 'https://api.descope.com/P2abcde12345',
167
+ 'sub' => 'U2abcde12345'
168
+ })
169
+ allow(@instance).to receive(:call).and_return(mock_response_no_cookies)
170
+ end
171
+
172
+ it 'provides helpful error message when no tokens are found' do
173
+ result = @instance.refresh_session(refresh_token: refresh_token, audience: audience)
174
+ expect(result).to be_a(Hash)
175
+ expect(result['sessionToken']).to be_nil
176
+ expect(result['refreshSessionToken']).to_not be_nil
177
+ end
178
+ end
179
+ end
180
+
181
+ describe 'validate_and_refresh_session with custom domain cookies' do
182
+ let(:session_token) { 'expired_session_token' }
183
+ let(:refresh_token) { 'valid_refresh_token' }
184
+ let(:audience) { nil }
185
+
186
+ context 'when session is expired and refresh uses custom domain cookies' do
187
+ let(:refresh_jwt) { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5kZXNjb3BlLmNvbSJ9.signature' }
188
+
189
+ let(:api_response_body) do
190
+ {
191
+ 'userId' => 'test123',
192
+ 'cookieExpiration' => 1640704758,
193
+ 'cookieDomain' => 'dev.lulukuku.com'
194
+ }
195
+ end
196
+
197
+ let(:set_cookie_headers) do
198
+ [
199
+ "DS=new_session_jwt; Path=/; Domain=dev.lulukuku.com; HttpOnly; Secure",
200
+ "DSR=#{refresh_jwt}; Path=/; Domain=dev.lulukuku.com; HttpOnly; Secure; Max-Age=2592000"
201
+ ]
202
+ end
203
+
204
+ let(:mock_response) do
205
+ double('response').tap do |response|
206
+ allow(response).to receive(:code).and_return(200)
207
+ allow(response).to receive(:body).and_return(api_response_body.to_json)
208
+ allow(response).to receive(:cookies).and_return({})
209
+ allow(response).to receive(:headers).and_return({ 'set-cookie' => set_cookie_headers })
210
+ end
211
+ end
212
+
213
+ before do
214
+ # Mock session validation to fail (expired token)
215
+ allow(@instance).to receive(:validate_session).and_raise(Descope::AuthException.new('Token expired'))
216
+
217
+ # Mock refresh_session to work with custom domain cookies
218
+ allow(@instance).to receive(:validate_refresh_token_not_nil).and_return(true)
219
+ allow(@instance).to receive(:validate_token).and_return({
220
+ 'iss' => 'https://api.descope.com/P2abcde12345',
221
+ 'sub' => 'U2abcde12345',
222
+ 'permissions' => [],
223
+ 'roles' => [],
224
+ 'tenants' => {}
225
+ })
226
+ allow(@instance).to receive(:call).and_return(mock_response)
227
+ end
228
+
229
+ it 'falls back to refresh_session when validate_session fails' do
230
+ expect(@instance).to receive(:refresh_session).with(
231
+ refresh_token: refresh_token,
232
+ audience: audience
233
+ ).and_call_original
234
+
235
+ result = @instance.validate_and_refresh_session(
236
+ session_token: session_token,
237
+ refresh_token: refresh_token,
238
+ audience: audience
239
+ )
240
+
241
+ expect(result).to be_a(Hash)
242
+ end
243
+ end
244
+ end
245
+ end
@@ -75,4 +75,96 @@ describe Descope::Api::V1::Management::Audit do
75
75
  expect(res['audits'][0]['projectId']).to eq('abc')
76
76
  end
77
77
  end
78
+
79
+ context '.create_event' do
80
+ it 'should respond to .audit_create_event' do
81
+ expect(@instance).to respond_to :audit_create_event
82
+ end
83
+
84
+ it 'should raise an error if type is not info, warn or error' do
85
+ expect do
86
+ @instance.audit_create_event(
87
+ action: 'get',
88
+ type: 'debug',
89
+ data: { key: 'value' },
90
+ user_id: 'user_id',
91
+ actor_id: 'actor_id',
92
+ tenant_id: 'tenant_id'
93
+ )
94
+ end.to raise_error(Descope::AuthException, 'type must be either info, warn or error')
95
+ end
96
+
97
+ it 'should raise an error if data is not a hash' do
98
+ expect do
99
+ @instance.audit_create_event(
100
+ action: 'get',
101
+ type: 'info',
102
+ data: 'data',
103
+ user_id: 'user_id',
104
+ actor_id: 'actor_id',
105
+ tenant_id: 'tenant_id'
106
+ )
107
+ end.to raise_error(Descope::AuthException, 'data must be provided as a key, value Hash')
108
+ end
109
+
110
+ it 'should raise an error if action is not provided' do
111
+ expect do
112
+ @instance.audit_create_event(
113
+ type: 'info',
114
+ data: { key: 'value' },
115
+ user_id: 'user_id',
116
+ actor_id: 'actor_id',
117
+ tenant_id: 'tenant_id'
118
+ )
119
+ end.to raise_error(Descope::AuthException, 'action must be provided')
120
+ end
121
+
122
+ it 'should raise an error if actor is not provided' do
123
+ expect do
124
+ @instance.audit_create_event(
125
+ action: 'get',
126
+ type: 'info',
127
+ data: { key: 'value' },
128
+ user_id: 'user_id',
129
+ tenant_id: 'tenant_id'
130
+ )
131
+ end.to raise_error(Descope::AuthException, 'actor_id must be provided')
132
+ end
133
+
134
+ it 'should raise an error if tenant_id is not provided' do
135
+ expect do
136
+ @instance.audit_create_event(
137
+ action: 'get',
138
+ type: 'info',
139
+ data: { key: 'value' },
140
+ user_id: 'user_id',
141
+ actor_id: 'actor_id'
142
+ )
143
+ end.to raise_error(Descope::AuthException, 'tenant_id must be provided')
144
+ end
145
+
146
+ it 'is expected to create an audit event' do
147
+ expect(@instance).to receive(:post).with(
148
+ '/v1/mgmt/audit/event',
149
+ {
150
+ action: 'get',
151
+ type: 'info',
152
+ actorId: 'actor_id',
153
+ data: { key: 'value' },
154
+ tenantId: 'tenant_id',
155
+ userId: 'user_id'
156
+ }
157
+ )
158
+ expect do
159
+ @instance.audit_create_event(
160
+ action: 'get',
161
+ type: 'info',
162
+ data: { key: 'value' },
163
+ user_id: 'user_id',
164
+ actor_id: 'actor_id',
165
+ tenant_id: 'tenant_id'
166
+ )
167
+ end.not_to raise_error
168
+ end
169
+ end
78
170
  end