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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yaml +2 -2
- data/.github/workflows/publish-gem.yaml +39 -7
- data/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/Gemfile +7 -7
- data/Gemfile.lock +70 -65
- data/README.md +175 -52
- data/descope.gemspec +25 -20
- data/examples/ruby/.ruby-version +1 -0
- data/examples/ruby/access_key_app.rb +4 -3
- data/examples/ruby/enchantedlink_app.rb +1 -0
- data/examples/ruby/magiclink_app.rb +1 -0
- data/examples/ruby/management/.ruby-version +1 -0
- data/examples/ruby/management/Gemfile +2 -2
- data/examples/ruby/management/access_key_app.rb +2 -0
- data/examples/ruby/management/audit_app.rb +32 -8
- data/examples/ruby/management/authz_app.rb +1 -0
- data/examples/ruby/management/flow_app.rb +1 -0
- data/examples/ruby/management/permission_app.rb +3 -2
- data/examples/ruby/management/role_app.rb +3 -2
- data/examples/ruby/management/tenant_app.rb +1 -0
- data/examples/ruby/management/user_app.rb +1 -0
- data/examples/ruby/oauth_app.rb +1 -0
- data/examples/ruby/otp_app.rb +38 -12
- data/examples/ruby/password_app.rb +8 -7
- data/examples/ruby/saml_app.rb +1 -0
- data/examples/ruby/version_check.rb +17 -0
- data/examples/ruby-on-rails-api/descope/Gemfile +9 -7
- data/examples/ruby-on-rails-api/descope/Gemfile.lock +121 -90
- data/examples/ruby-on-rails-api/descope/README.md +18 -18
- data/examples/ruby-on-rails-api/descope/app/assets/builds/application.css +20092 -23
- data/examples/ruby-on-rails-api/descope/app/assets/builds/application.js +0 -1
- data/examples/ruby-on-rails-api/descope/app/assets/builds/components/index.js +0 -14
- data/examples/ruby-on-rails-api/descope/package-lock.json +1073 -19302
- data/examples/ruby-on-rails-api/descope/package.json +8 -16
- data/examples/ruby-on-rails-api/descope/yarn.lock +557 -10641
- data/lib/descope/api/v1/auth/enchantedlink.rb +3 -1
- data/lib/descope/api/v1/auth/magiclink.rb +3 -1
- data/lib/descope/api/v1/auth/otp.rb +24 -15
- data/lib/descope/api/v1/auth/password.rb +6 -2
- data/lib/descope/api/v1/auth/totp.rb +3 -1
- data/lib/descope/api/v1/auth.rb +64 -32
- data/lib/descope/api/v1/management/audit.rb +24 -0
- data/lib/descope/api/v1/management/common.rb +21 -5
- data/lib/descope/api/v1/management/sso_application.rb +236 -0
- data/lib/descope/api/v1/management/sso_settings.rb +2 -24
- data/lib/descope/api/v1/management/user.rb +151 -13
- data/lib/descope/api/v1/management.rb +2 -0
- data/lib/descope/api/v1/session.rb +37 -4
- data/lib/descope/mixins/common.rb +6 -2
- data/lib/descope/mixins/http.rb +60 -9
- data/lib/descope/mixins/initializer.rb +2 -1
- data/lib/descope/mixins/logging.rb +12 -4
- data/lib/descope/mixins/validation.rb +21 -6
- data/lib/descope/version.rb +1 -1
- data/spec/descope/api/v1/auth_spec.rb +29 -0
- data/spec/descope/api/v1/auth_token_extraction_spec.rb +126 -0
- data/spec/descope/api/v1/session_refresh_spec.rb +98 -0
- data/spec/factories/user.rb +1 -1
- data/spec/integration/lib.descope/api/v1/auth/enchantedlink_spec.rb +1 -1
- data/spec/integration/lib.descope/api/v1/auth/magiclink_spec.rb +1 -1
- data/spec/integration/lib.descope/api/v1/auth/otp_spec.rb +73 -8
- data/spec/integration/lib.descope/api/v1/auth/session_spec.rb +49 -0
- data/spec/integration/lib.descope/api/v1/auth/totp_spec.rb +1 -1
- data/spec/integration/lib.descope/api/v1/management/access_key_spec.rb +3 -0
- data/spec/integration/lib.descope/api/v1/management/audit_spec.rb +38 -0
- data/spec/integration/lib.descope/api/v1/management/authz_spec.rb +2 -0
- data/spec/integration/lib.descope/api/v1/management/flow_spec.rb +3 -1
- data/spec/integration/lib.descope/api/v1/management/permissions_spec.rb +4 -2
- data/spec/integration/lib.descope/api/v1/management/project_spec.rb +2 -0
- data/spec/integration/lib.descope/api/v1/management/roles_spec.rb +3 -1
- data/spec/integration/lib.descope/api/v1/management/user_spec.rb +55 -6
- data/spec/lib.descope/api/v1/auth/enchantedlink_spec.rb +11 -2
- data/spec/lib.descope/api/v1/auth/otp_spec.rb +176 -18
- data/spec/lib.descope/api/v1/auth/password_spec.rb +10 -1
- data/spec/lib.descope/api/v1/auth_spec.rb +168 -6
- data/spec/lib.descope/api/v1/cookie_domain_fix_integration_spec.rb +245 -0
- data/spec/lib.descope/api/v1/management/audit_spec.rb +92 -0
- data/spec/lib.descope/api/v1/management/sso_application_spec.rb +217 -0
- data/spec/lib.descope/api/v1/management/sso_settings_spec.rb +2 -2
- data/spec/lib.descope/api/v1/management/user_spec.rb +134 -46
- data/spec/lib.descope/api/v1/session_spec.rb +119 -6
- data/spec/lib.descope/mixins/http_spec.rb +218 -0
- data/spec/support/client_config.rb +0 -1
- data/spec/support/utils.rb +6 -0
- metadata +34 -137
- data/examples/ruby-on-rails-api/descope/app/assets/builds/reportWebVitals.js +0 -211
- 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 = {
|
|
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 = {
|
|
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(:
|
|
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 = {
|
|
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(:
|
|
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
|