descope 1.0.6 → 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 +10 -3
- data/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/Gemfile +7 -7
- data/Gemfile.lock +68 -55
- data/README.md +159 -51
- data/examples/ruby-on-rails-api/descope/Gemfile +8 -8
- data/examples/ruby-on-rails-api/descope/Gemfile.lock +1 -1
- data/examples/ruby-on-rails-api/descope/package-lock.json +187 -131
- data/examples/ruby-on-rails-api/descope/package.json +1 -1
- data/examples/ruby-on-rails-api/descope/yarn.lock +182 -84
- 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 +3 -1
- 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 +47 -12
- data/lib/descope/api/v1/management/common.rb +20 -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 +1 -0
- 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/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 +1 -1
- 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 +5 -3
- 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 +2 -0
- 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/password_spec.rb +10 -1
- data/spec/lib.descope/api/v1/auth_spec.rb +167 -5
- data/spec/lib.descope/api/v1/cookie_domain_fix_integration_spec.rb +245 -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 +13 -8
data/lib/descope/mixins/http.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require
|
|
2
|
+
require 'descope/mixins/common'
|
|
3
|
+
require 'addressable/uri'
|
|
3
4
|
require 'retryable'
|
|
4
5
|
require_relative '../exception'
|
|
5
6
|
|
|
@@ -7,6 +8,7 @@ module Descope
|
|
|
7
8
|
module Mixins
|
|
8
9
|
# HTTP-related methods
|
|
9
10
|
module HTTP
|
|
11
|
+
include Descope::Mixins::Common
|
|
10
12
|
attr_accessor :headers, :base_uri, :timeout, :retry_count
|
|
11
13
|
|
|
12
14
|
DEFAULT_RETRIES = 3
|
|
@@ -44,13 +46,61 @@ module Descope
|
|
|
44
46
|
}
|
|
45
47
|
end
|
|
46
48
|
|
|
47
|
-
def safe_parse_json(body)
|
|
49
|
+
def safe_parse_json(body, cookies: {}, headers: {})
|
|
48
50
|
@logger.debug "response => #{JSON.parse(body.to_s)}"
|
|
49
|
-
JSON.parse(body.to_s)
|
|
51
|
+
res = JSON.parse(body.to_s)
|
|
52
|
+
|
|
53
|
+
# Handle DS and DSR cookies in response.
|
|
54
|
+
# First check RestClient's cookies (works for same-domain cookies)
|
|
55
|
+
extracted_cookies = {}
|
|
56
|
+
if cookies.key?(SESSION_COOKIE_NAME)
|
|
57
|
+
extracted_cookies[SESSION_COOKIE_NAME] = cookies[SESSION_COOKIE_NAME]
|
|
58
|
+
end
|
|
59
|
+
if cookies.key?(REFRESH_SESSION_COOKIE_NAME)
|
|
60
|
+
extracted_cookies[REFRESH_SESSION_COOKIE_NAME] = cookies[REFRESH_SESSION_COOKIE_NAME]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# If no cookies found via RestClient, parse Set-Cookie headers directly
|
|
64
|
+
# This handles custom domain cookies that RestClient filters out
|
|
65
|
+
if extracted_cookies.empty? && headers.respond_to?(:[])
|
|
66
|
+
set_cookie_headers = headers['set-cookie'] || headers['Set-Cookie'] || []
|
|
67
|
+
set_cookie_headers = [set_cookie_headers] unless set_cookie_headers.is_a?(Array)
|
|
68
|
+
|
|
69
|
+
set_cookie_headers.each do |cookie_header|
|
|
70
|
+
next unless cookie_header.is_a?(String)
|
|
71
|
+
|
|
72
|
+
# Parse DS cookie (session token)
|
|
73
|
+
if cookie_header.include?("#{SESSION_COOKIE_NAME}=")
|
|
74
|
+
cookie_value = parse_cookie_value(cookie_header, SESSION_COOKIE_NAME)
|
|
75
|
+
extracted_cookies[SESSION_COOKIE_NAME] = cookie_value if cookie_value
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Parse DSR cookie (refresh token)
|
|
79
|
+
if cookie_header.include?("#{REFRESH_SESSION_COOKIE_NAME}=")
|
|
80
|
+
cookie_value = parse_cookie_value(cookie_header, REFRESH_SESSION_COOKIE_NAME)
|
|
81
|
+
extracted_cookies[REFRESH_SESSION_COOKIE_NAME] = cookie_value if cookie_value
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Add extracted cookies to response if any were found
|
|
87
|
+
unless extracted_cookies.empty?
|
|
88
|
+
res['cookies'] = extracted_cookies
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
res
|
|
50
92
|
rescue JSON::ParserError
|
|
51
93
|
body
|
|
52
94
|
end
|
|
53
95
|
|
|
96
|
+
def parse_cookie_value(cookie_header, cookie_name)
|
|
97
|
+
# Extract cookie value from Set-Cookie header
|
|
98
|
+
# Format: "cookieName=cookieValue; attribute1=value1; attribute2=value2"
|
|
99
|
+
# Only match valid cookie value characters (RFC 6265: exclude whitespace, semicolon, comma)
|
|
100
|
+
match = cookie_header.match(/#{Regexp.escape(cookie_name)}=([^;]+)/)
|
|
101
|
+
match ? match[1].strip : nil
|
|
102
|
+
end
|
|
103
|
+
|
|
54
104
|
def encode_uri(uri)
|
|
55
105
|
encoded_uri = base_uri ? Addressable::URI.parse(uri).normalize : Addressable::URI.escape(uri)
|
|
56
106
|
@logger.debug "will call #{url(encoded_uri)}"
|
|
@@ -94,11 +144,12 @@ module Descope
|
|
|
94
144
|
call(method, encode_uri(uri), timeout, @headers, body.to_json)
|
|
95
145
|
end
|
|
96
146
|
|
|
97
|
-
raise Descope::Unsupported.new(
|
|
147
|
+
raise Descope::Unsupported.new('No response from server', code: 400) unless result.respond_to?(:code)
|
|
98
148
|
|
|
99
149
|
@logger.info("API Request: [#{method}] #{uri} - Response Code: #{result.code}")
|
|
150
|
+
|
|
100
151
|
case result.code
|
|
101
|
-
when 200...226 then safe_parse_json(result.body)
|
|
152
|
+
when 200...226 then safe_parse_json(result.body, cookies: result.cookies, headers: result.headers)
|
|
102
153
|
when 400 then raise Descope::BadRequest.new(result.body, code: result.code, headers: result.headers)
|
|
103
154
|
when 401 then raise Descope::Unauthorized.new(result.body, code: result.code, headers: result.headers)
|
|
104
155
|
when 403 then raise Descope::AccessDenied.new(result.body, code: result.code, headers: result.headers)
|
|
@@ -113,10 +164,10 @@ module Descope
|
|
|
113
164
|
|
|
114
165
|
def call(method, url, timeout, headers, body = nil)
|
|
115
166
|
RestClient::Request.execute(
|
|
116
|
-
method
|
|
117
|
-
url
|
|
118
|
-
timeout
|
|
119
|
-
headers
|
|
167
|
+
method: method,
|
|
168
|
+
url: url,
|
|
169
|
+
timeout: timeout,
|
|
170
|
+
headers: headers,
|
|
120
171
|
payload: body
|
|
121
172
|
)
|
|
122
173
|
rescue RestClient::Exception => e
|
|
@@ -13,10 +13,11 @@ module Descope
|
|
|
13
13
|
@base_uri = base_url(options)
|
|
14
14
|
@headers = client_headers
|
|
15
15
|
@project_id = options[:project_id] || ENV['DESCOPE_PROJECT_ID'] || ''
|
|
16
|
+
@headers['x-descope-project-id'] = @project_id
|
|
16
17
|
@public_key = options[:public_key] || ENV['DESCOPE_PUBLIC_KEY']
|
|
17
18
|
@mlock = Mutex.new
|
|
18
19
|
log_level = options[:log_level] || ENV['DESCOPE_LOG_LEVEL'] || 'info'
|
|
19
|
-
@logger ||= Descope::Mixins::Logging.logger_for(self.class.name, log_level)
|
|
20
|
+
@logger ||= Descope::Mixins::Logging.logger_for(self.class.name, log_level, @project_id)
|
|
20
21
|
|
|
21
22
|
@logger.debug("Initializing Descope API with project_id: #{@project_id} and base_uri: #{@base_uri}")
|
|
22
23
|
|
|
@@ -7,21 +7,29 @@ module Descope
|
|
|
7
7
|
|
|
8
8
|
def logger
|
|
9
9
|
# This is the magical bit that gets mixed into the other modules
|
|
10
|
-
@logger ||= Logging.logger_for(self.class.name, 'info')
|
|
10
|
+
@logger ||= Logging.logger_for(self.class.name, 'info', @project_id)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
# Use a hash class-ivar to cache a unique Logger per class:
|
|
14
14
|
@loggers = {}
|
|
15
15
|
|
|
16
16
|
class << self
|
|
17
|
-
def logger_for(classname, level)
|
|
18
|
-
|
|
17
|
+
def logger_for(classname, level, project_id = nil)
|
|
18
|
+
key = "#{classname}-#{project_id}"
|
|
19
|
+
@loggers[key] ||= configure_logger_for(classname, level, project_id)
|
|
19
20
|
end
|
|
20
21
|
|
|
21
|
-
def configure_logger_for(classname, level = 'info')
|
|
22
|
+
def configure_logger_for(classname, level = 'info', project_id = nil)
|
|
22
23
|
logger = Logger.new(STDOUT)
|
|
23
24
|
logger.level = Object.const_get("Logger::#{level.upcase}")
|
|
24
25
|
logger.progname = classname
|
|
26
|
+
|
|
27
|
+
# Adding Custom Formatter for Project ID
|
|
28
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
|
29
|
+
project_info = project_id ? "PRID: #{project_id}" : ""
|
|
30
|
+
"[#{datetime}] #{severity} #{project_info} #{progname}: #{msg}\n"
|
|
31
|
+
end
|
|
32
|
+
|
|
25
33
|
logger
|
|
26
34
|
end
|
|
27
35
|
end
|
data/lib/descope/version.rb
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Descope::Api::V1::Auth do
|
|
6
|
+
let(:client) { Class.new { include Descope::Api::V1::Auth }.new }
|
|
7
|
+
let(:valid_token) { 'valid_token' }
|
|
8
|
+
let(:refresh_token) { 'refresh_token' }
|
|
9
|
+
|
|
10
|
+
describe 'token extraction with empty strings' do
|
|
11
|
+
it 'handles empty string tokens correctly' do
|
|
12
|
+
response_body = {
|
|
13
|
+
'sessionJwt' => '',
|
|
14
|
+
'refreshJwt' => '',
|
|
15
|
+
'cookies' => {
|
|
16
|
+
'DS' => valid_token,
|
|
17
|
+
'DSR' => refresh_token
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
allow(client).to receive(:validate_token).and_return({ 'sub' => 'user123', 'iss' => 'test_project_id' })
|
|
22
|
+
|
|
23
|
+
result = client.generate_jwt_response(response_body: response_body)
|
|
24
|
+
|
|
25
|
+
expect(result).to have_key('sessionToken')
|
|
26
|
+
expect(result).to have_key('refreshSessionToken')
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Descope::Api::V1::Auth do
|
|
6
|
+
let(:client) { Descope::Client.new(project_id: 'test_project_id', management_key: 'test_key') }
|
|
7
|
+
let(:valid_session_token) { 'valid.session.token' }
|
|
8
|
+
let(:valid_refresh_token) { 'valid.refresh.token' }
|
|
9
|
+
|
|
10
|
+
describe '#generate_auth_info (private method)' do
|
|
11
|
+
context 'session token extraction' do
|
|
12
|
+
it 'extracts session token from sessionJwt field' do
|
|
13
|
+
response_body = {
|
|
14
|
+
'sessionJwt' => valid_session_token,
|
|
15
|
+
'refreshJwt' => valid_refresh_token,
|
|
16
|
+
'cookies' => {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
allow(client).to receive(:validate_token).and_return({ 'sub' => 'user123', 'iss' => 'test_project_id' })
|
|
20
|
+
|
|
21
|
+
result = client.send(:generate_auth_info, response_body, nil, true, nil)
|
|
22
|
+
|
|
23
|
+
expect(result).to have_key('sessionToken')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'extracts session token from cookies with DS name' do
|
|
27
|
+
response_body = {
|
|
28
|
+
'sessionJwt' => '',
|
|
29
|
+
'refreshJwt' => valid_refresh_token,
|
|
30
|
+
'cookies' => {
|
|
31
|
+
'DS' => valid_session_token
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
allow(client).to receive(:validate_token).and_return({ 'sub' => 'user123', 'iss' => 'test_project_id' })
|
|
36
|
+
|
|
37
|
+
result = client.send(:generate_auth_info, response_body, nil, true, nil)
|
|
38
|
+
|
|
39
|
+
expect(result).to have_key('sessionToken')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'extracts session token from cookies with SESSION_COOKIE_NAME' do
|
|
43
|
+
stub_const('Descope::Api::V1::Auth::SESSION_COOKIE_NAME', 'CustomSession')
|
|
44
|
+
|
|
45
|
+
response_body = {
|
|
46
|
+
'sessionJwt' => '',
|
|
47
|
+
'refreshJwt' => valid_refresh_token,
|
|
48
|
+
'cookies' => {
|
|
49
|
+
'CustomSession' => valid_session_token
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
allow(client).to receive(:validate_token).and_return({ 'sub' => 'user123', 'iss' => 'test_project_id' })
|
|
54
|
+
|
|
55
|
+
result = client.send(:generate_auth_info, response_body, nil, true, nil)
|
|
56
|
+
|
|
57
|
+
expect(result).to have_key('sessionToken')
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
context 'refresh token extraction' do
|
|
62
|
+
it 'extracts refresh token from refreshJwt field' do
|
|
63
|
+
response_body = {
|
|
64
|
+
'sessionJwt' => valid_session_token,
|
|
65
|
+
'refreshJwt' => valid_refresh_token,
|
|
66
|
+
'cookies' => {}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
allow(client).to receive(:validate_token).and_return({ 'sub' => 'user123', 'iss' => 'test_project_id' })
|
|
70
|
+
|
|
71
|
+
result = client.send(:generate_auth_info, response_body, nil, true, nil)
|
|
72
|
+
|
|
73
|
+
expect(result).to have_key('refreshSessionToken')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'extracts refresh token from cookies when refreshJwt is empty' do
|
|
77
|
+
stub_const('Descope::Api::V1::Auth::REFRESH_SESSION_COOKIE_NAME', 'DSR')
|
|
78
|
+
|
|
79
|
+
response_body = {
|
|
80
|
+
'sessionJwt' => valid_session_token,
|
|
81
|
+
'refreshJwt' => '',
|
|
82
|
+
'cookies' => {
|
|
83
|
+
'DSR' => valid_refresh_token
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
allow(client).to receive(:validate_token).and_return({ 'sub' => 'user123', 'iss' => 'test_project_id' })
|
|
88
|
+
|
|
89
|
+
result = client.send(:generate_auth_info, response_body, nil, true, nil)
|
|
90
|
+
|
|
91
|
+
expect(result).to have_key('refreshSessionToken')
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'falls back to parameter refresh token when cookie has empty string' do
|
|
95
|
+
response_body = {
|
|
96
|
+
'sessionJwt' => valid_session_token,
|
|
97
|
+
'refreshJwt' => '',
|
|
98
|
+
'cookies' => {
|
|
99
|
+
'DSR' => ''
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
allow(client).to receive(:validate_token).and_return({ 'sub' => 'user123', 'iss' => 'test_project_id' })
|
|
104
|
+
|
|
105
|
+
result = client.send(:generate_auth_info, response_body, valid_refresh_token, true, nil)
|
|
106
|
+
|
|
107
|
+
expect(result).to have_key('refreshSessionToken')
|
|
108
|
+
expect(client).to have_received(:validate_token).with(valid_refresh_token, nil)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it 'raises error when no refresh token is available' do
|
|
112
|
+
response_body = {
|
|
113
|
+
'sessionJwt' => valid_session_token,
|
|
114
|
+
'refreshJwt' => '',
|
|
115
|
+
'cookies' => {}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
allow(client).to receive(:validate_token).and_return({ 'sub' => 'user123', 'iss' => 'test_project_id' })
|
|
119
|
+
|
|
120
|
+
expect {
|
|
121
|
+
client.send(:generate_auth_info, response_body, nil, true, nil)
|
|
122
|
+
}.to raise_error(Descope::AuthException, /Could not find refresh token/)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Descope::Api::V1::Session do
|
|
6
|
+
let(:client) { Descope::Client.new(project_id: 'test_project_id', management_key: 'test_key') }
|
|
7
|
+
let(:valid_token) { 'valid.jwt.token' }
|
|
8
|
+
let(:refresh_token) { 'refresh.jwt.token' }
|
|
9
|
+
|
|
10
|
+
describe '#refresh_session' do
|
|
11
|
+
context 'when refresh token is in cookie' do
|
|
12
|
+
it 'uses the cookie refresh token' do
|
|
13
|
+
response_body = {
|
|
14
|
+
'sessionJwt' => '',
|
|
15
|
+
'refreshJwt' => '',
|
|
16
|
+
'cookieData' => {
|
|
17
|
+
'DSR' => refresh_token
|
|
18
|
+
},
|
|
19
|
+
'cookies' => {
|
|
20
|
+
'DS' => valid_token
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
allow(client).to receive(:post).and_return(response_body)
|
|
25
|
+
allow(client).to receive(:validate_token).and_return({ 'sub' => 'user123' })
|
|
26
|
+
|
|
27
|
+
result = client.refresh_session(refresh_token: refresh_token)
|
|
28
|
+
|
|
29
|
+
expect(result).to be_a(Hash)
|
|
30
|
+
expect(client).to have_received(:validate_token).with(refresh_token, nil)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context 'when refresh token is in response body' do
|
|
35
|
+
it 'uses the refreshJwt from response body' do
|
|
36
|
+
response_body = {
|
|
37
|
+
'sessionJwt' => '',
|
|
38
|
+
'refreshJwt' => refresh_token,
|
|
39
|
+
'cookieData' => {},
|
|
40
|
+
'cookies' => {
|
|
41
|
+
'DS' => valid_token
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
allow(client).to receive(:post).and_return(response_body)
|
|
46
|
+
allow(client).to receive(:validate_token).and_return({ 'sub' => 'user123' })
|
|
47
|
+
|
|
48
|
+
result = client.refresh_session(refresh_token: 'old_refresh_token')
|
|
49
|
+
|
|
50
|
+
expect(result).to be_a(Hash)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context 'when refresh token is only in parameter' do
|
|
55
|
+
it 'falls back to the parameter refresh token' do
|
|
56
|
+
response_body = {
|
|
57
|
+
'sessionJwt' => '',
|
|
58
|
+
'refreshJwt' => '',
|
|
59
|
+
'cookieData' => {},
|
|
60
|
+
'cookies' => {
|
|
61
|
+
'DS' => valid_token
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
allow(client).to receive(:post).and_return(response_body)
|
|
66
|
+
allow(client).to receive(:validate_token).and_return({ 'sub' => 'user123' })
|
|
67
|
+
|
|
68
|
+
result = client.refresh_session(refresh_token: refresh_token)
|
|
69
|
+
|
|
70
|
+
expect(result).to be_a(Hash)
|
|
71
|
+
expect(client).to have_received(:validate_token).with(refresh_token, nil).at_least(:once)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
context 'when refresh token values are empty strings' do
|
|
76
|
+
it 'falls back to parameter refresh token when cookie and body have empty strings' do
|
|
77
|
+
response_body = {
|
|
78
|
+
'sessionJwt' => '',
|
|
79
|
+
'refreshJwt' => '',
|
|
80
|
+
'cookieData' => {
|
|
81
|
+
'DSR' => ''
|
|
82
|
+
},
|
|
83
|
+
'cookies' => {
|
|
84
|
+
'DS' => valid_token
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
allow(client).to receive(:post).and_return(response_body)
|
|
89
|
+
allow(client).to receive(:validate_token).and_return({ 'sub' => 'user123' })
|
|
90
|
+
|
|
91
|
+
result = client.refresh_session(refresh_token: refresh_token)
|
|
92
|
+
|
|
93
|
+
expect(result).to be_a(Hash)
|
|
94
|
+
expect(client).to have_received(:validate_token).with(refresh_token, nil).at_least(:once)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
data/spec/factories/user.rb
CHANGED
|
@@ -10,7 +10,7 @@ FactoryBot.define do
|
|
|
10
10
|
phone { "+1#{Faker::Number.number(digits: 10)}" }
|
|
11
11
|
name { Faker::Name.name }
|
|
12
12
|
given_name { Faker::Name.first_name }
|
|
13
|
-
middle_name {
|
|
13
|
+
middle_name { "#{SpecUtils.build_prefix}Ruby-SDK-User" }
|
|
14
14
|
family_name { Faker::Name.last_name }
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -60,7 +60,7 @@ describe Descope::Api::V1::Auth::EnchantedLink do
|
|
|
60
60
|
@client.logger.info('Cleaning up test users...')
|
|
61
61
|
all_users = @client.search_all_users
|
|
62
62
|
all_users['users'].each do |user|
|
|
63
|
-
if user['middleName'] ==
|
|
63
|
+
if user['middleName'] == "#{SpecUtils.build_prefix}Ruby-SDK-User"
|
|
64
64
|
@client.logger.info("Deleting ruby spec test user #{user['loginIds'][0]}")
|
|
65
65
|
@client.delete_user(user['loginIds'][0])
|
|
66
66
|
end
|
|
@@ -11,7 +11,7 @@ describe Descope::Api::V1::Auth::MagicLink do
|
|
|
11
11
|
@client.logger.info('Cleaning up test users...')
|
|
12
12
|
all_users = @client.search_all_users
|
|
13
13
|
all_users['users'].each do |user|
|
|
14
|
-
if user['middleName'] ==
|
|
14
|
+
if user['middleName'] == "#{SpecUtils.build_prefix}Ruby-SDK-User"
|
|
15
15
|
@client.logger.info("Deleting ruby spec test user #{user['loginIds'][0]}")
|
|
16
16
|
@client.delete_user(user['loginIds'][0])
|
|
17
17
|
end
|
|
@@ -19,7 +19,7 @@ describe Descope::Api::V1::Auth::OTP do
|
|
|
19
19
|
@client.logger.info('Cleaning up test users...')
|
|
20
20
|
all_users = @client.search_all_users
|
|
21
21
|
all_users['users'].each do |user|
|
|
22
|
-
if user['middleName'] ==
|
|
22
|
+
if user['middleName'] == "#{SpecUtils.build_prefix}Ruby-SDK-User"
|
|
23
23
|
@client.logger.info("Deleting ruby spec test user #{user['loginIds'][0]}")
|
|
24
24
|
@client.delete_user(user['loginIds'][0])
|
|
25
25
|
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Descope::Api::V1::Session do
|
|
6
|
+
before(:all) do
|
|
7
|
+
@client = DescopeClient.new(Configuration.config)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
after(:all) do
|
|
11
|
+
@client.logger.info('Cleaning up test users...')
|
|
12
|
+
all_users = @client.search_all_users
|
|
13
|
+
all_users['users'].each do |user|
|
|
14
|
+
if user['middleName'] == "#{SpecUtils.build_prefix}Ruby-SDK-User"
|
|
15
|
+
@client.logger.info("Deleting ruby spec test user #{user['loginIds'][0]}")
|
|
16
|
+
@client.delete_user(user['loginIds'][0])
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
context 'test session methods' do
|
|
22
|
+
it 'should refresh session with refresh token' do
|
|
23
|
+
@password = SpecUtils.generate_password
|
|
24
|
+
user = build(:user)
|
|
25
|
+
|
|
26
|
+
@client.logger.info('1. Sign up with password')
|
|
27
|
+
res = @client.password_sign_up(login_id: user[:login_id], password: @password, user:)
|
|
28
|
+
@client.logger.info("sign up with password res: #{res}")
|
|
29
|
+
original_refresh_token = res[REFRESH_SESSION_TOKEN_NAME]['jwt']
|
|
30
|
+
|
|
31
|
+
@client.logger.info('2. Sign in with password')
|
|
32
|
+
login_res = @client.password_sign_in(login_id: user[:login_id], password: @password)
|
|
33
|
+
@client.logger.info("sign_in res: #{login_res}")
|
|
34
|
+
|
|
35
|
+
@client.logger.info('3. sleep 1 second before calling refresh_session')
|
|
36
|
+
sleep(1)
|
|
37
|
+
|
|
38
|
+
@client.logger.info('4. Refresh session')
|
|
39
|
+
refresh_session_res = @client.refresh_session(refresh_token: login_res[REFRESH_SESSION_TOKEN_NAME]['jwt'])
|
|
40
|
+
@client.logger.info("refresh_session_res: #{refresh_session_res}")
|
|
41
|
+
|
|
42
|
+
new_refresh_token = refresh_session_res[REFRESH_SESSION_TOKEN_NAME]['jwt']
|
|
43
|
+
@client.logger.info("new_refresh_token: #{new_refresh_token}")
|
|
44
|
+
|
|
45
|
+
@client.logger.info('5. Check new refresh token is not the same as the original one')
|
|
46
|
+
expect(original_refresh_token).not_to eq(new_refresh_token)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -12,7 +12,7 @@ describe Descope::Api::V1::Auth::TOTP do
|
|
|
12
12
|
@client.logger.info('Cleaning up test users...')
|
|
13
13
|
all_users = @client.search_all_users
|
|
14
14
|
all_users['users'].each do |user|
|
|
15
|
-
if user['middleName'] ==
|
|
15
|
+
if user['middleName'] == "#{SpecUtils.build_prefix}Ruby-SDK-User"
|
|
16
16
|
@client.logger.info("Deleting ruby spec test user #{user['loginIds'][0]}")
|
|
17
17
|
@client.delete_user(user['loginIds'][0])
|
|
18
18
|
end
|
|
@@ -4,6 +4,8 @@ require 'spec_helper'
|
|
|
4
4
|
|
|
5
5
|
describe Descope::Api::V1::Management::AccessKey do
|
|
6
6
|
before(:all) do
|
|
7
|
+
raise 'DESCOPE_MANAGEMENT_KEY is not set' if ENV['DESCOPE_MANAGEMENT_KEY'].nil?
|
|
8
|
+
|
|
7
9
|
@client = DescopeClient.new(Configuration.config)
|
|
8
10
|
end
|
|
9
11
|
|
|
@@ -28,6 +30,7 @@ describe Descope::Api::V1::Management::AccessKey do
|
|
|
28
30
|
@tenant_id = @client.create_tenant(name: 'some-new-tenant')['id']
|
|
29
31
|
@client.logger.info('creating access key')
|
|
30
32
|
@access_key = @client.create_access_key(name: @key_name, key_tenants: [{ tenant_id: @tenant_id }])
|
|
33
|
+
@client.logger.info("waiting for access key #{@access_key['key']['id']} to be active 60 seconds")
|
|
31
34
|
sleep 60
|
|
32
35
|
end
|
|
33
36
|
|
|
@@ -4,6 +4,8 @@ require 'spec_helper'
|
|
|
4
4
|
|
|
5
5
|
describe Descope::Api::V1::Management::Audit do
|
|
6
6
|
before(:all) do
|
|
7
|
+
raise 'DESCOPE_MANAGEMENT_KEY is not set' if ENV['DESCOPE_MANAGEMENT_KEY'].nil?
|
|
8
|
+
|
|
7
9
|
@client = DescopeClient.new(Configuration.config)
|
|
8
10
|
@client.logger.info('Deleting all tenants for Ruby SDK...')
|
|
9
11
|
@client.search_all_tenants(names: ['Ruby-SDK-test'])['tenants'].each do |tenant|
|
|
@@ -16,7 +18,7 @@ describe Descope::Api::V1::Management::Audit do
|
|
|
16
18
|
after(:all) do
|
|
17
19
|
all_users = @client.search_all_users
|
|
18
20
|
all_users['users'].each do |user|
|
|
19
|
-
if user['middleName'] ==
|
|
21
|
+
if user['middleName'] == "#{SpecUtils.build_prefix}Ruby-SDK-User"
|
|
20
22
|
puts "Deleting ruby spec test user #{user['loginIds'][0]}"
|
|
21
23
|
@client.delete_user(user['loginIds'][0])
|
|
22
24
|
end
|
|
@@ -39,14 +41,14 @@ describe Descope::Api::V1::Management::Audit do
|
|
|
39
41
|
created_user = @client.create_user(**user)['user']
|
|
40
42
|
|
|
41
43
|
expect do
|
|
42
|
-
|
|
44
|
+
@client.audit_create_event(
|
|
45
|
+
user_id: created_user['loginId'],
|
|
43
46
|
action: 'pencil.created',
|
|
44
47
|
type: 'info',
|
|
45
48
|
tenant_id:,
|
|
46
49
|
actor_id: created_user['loginIds'][0],
|
|
47
50
|
data: { 'key' => 'value' }
|
|
48
51
|
)
|
|
49
|
-
expect(res).to eq({})
|
|
50
52
|
end.not_to raise_error
|
|
51
53
|
end
|
|
52
54
|
end
|
|
@@ -4,6 +4,8 @@ require 'spec_helper'
|
|
|
4
4
|
|
|
5
5
|
describe Descope::Api::V1::Management::Flow do
|
|
6
6
|
before(:all) do
|
|
7
|
+
raise 'DESCOPE_MANAGEMENT_KEY is not set' if ENV['DESCOPE_MANAGEMENT_KEY'].nil?
|
|
8
|
+
|
|
7
9
|
@client = DescopeClient.new(Configuration.config)
|
|
8
10
|
end
|
|
9
11
|
|
|
@@ -38,7 +40,7 @@ describe Descope::Api::V1::Management::Flow do
|
|
|
38
40
|
it 'should import the current project theme' do
|
|
39
41
|
export_theme = @client.export_theme
|
|
40
42
|
export_theme_current_version = export_theme['theme']['version']
|
|
41
|
-
imported_theme = @client.import_theme(export_theme)
|
|
43
|
+
imported_theme = @client.import_theme(export_theme['theme'])
|
|
42
44
|
expect(imported_theme['theme']['version']).to be(export_theme_current_version + 1)
|
|
43
45
|
end
|
|
44
46
|
end
|
|
@@ -4,9 +4,11 @@ require 'spec_helper'
|
|
|
4
4
|
|
|
5
5
|
describe Descope::Api::V1::Management::Permission do
|
|
6
6
|
before(:all) do
|
|
7
|
+
raise 'DESCOPE_MANAGEMENT_KEY is not set' if ENV['DESCOPE_MANAGEMENT_KEY'].nil?
|
|
8
|
+
|
|
7
9
|
@client = DescopeClient.new(Configuration.config)
|
|
8
10
|
@client.load_all_permissions['permissions'].each do |perm|
|
|
9
|
-
if perm['description'] ==
|
|
11
|
+
if perm['description'] == "#{SpecUtils.build_prefix} Ruby SDK"
|
|
10
12
|
puts "Deleting permission: #{perm['name']}"
|
|
11
13
|
@client.delete_permission(perm['name'])
|
|
12
14
|
end
|
|
@@ -14,7 +16,7 @@ describe Descope::Api::V1::Management::Permission do
|
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
it 'should create update and delete a permission' do
|
|
17
|
-
@client.create_permission(name: 'test_permission', description:
|
|
19
|
+
@client.create_permission(name: 'test_permission', description: "#{SpecUtils.build_prefix} Ruby SDK")
|
|
18
20
|
all_permissions = @client.load_all_permissions['permissions']
|
|
19
21
|
expect(all_permissions.any? { |perm| perm['name'] == 'test_permission' }).to eq(true)
|
|
20
22
|
@client.update_permission(name: 'test_permission', new_name: 'test_permission_2')
|
|
@@ -4,6 +4,8 @@ require 'spec_helper'
|
|
|
4
4
|
|
|
5
5
|
describe Descope::Api::V1::Management::Project do
|
|
6
6
|
before(:all) do
|
|
7
|
+
raise 'DESCOPE_MANAGEMENT_KEY is not set' if ENV['DESCOPE_MANAGEMENT_KEY'].nil?
|
|
8
|
+
|
|
7
9
|
@client = DescopeClient.new(Configuration.config)
|
|
8
10
|
@export_output = @client.export_project
|
|
9
11
|
end
|
|
@@ -4,6 +4,8 @@ require 'spec_helper'
|
|
|
4
4
|
|
|
5
5
|
describe Descope::Api::V1::Management::Role do
|
|
6
6
|
before(:all) do
|
|
7
|
+
raise 'DESCOPE_MANAGEMENT_KEY is not set' if ENV['DESCOPE_MANAGEMENT_KEY'].nil?
|
|
8
|
+
|
|
7
9
|
@client = DescopeClient.new(Configuration.config)
|
|
8
10
|
@client.logger.info('Staring cleanup before tests...')
|
|
9
11
|
@client.logger.info('Deleting all permissions for Ruby SDK...')
|