descope 1.0.4
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 +7 -0
- data/.github/workflows/ci.yaml +54 -0
- data/.gitignore +59 -0
- data/.release-please-manifest.json +3 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +10 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +90 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +204 -0
- data/LICENSE +21 -0
- data/README.md +1171 -0
- data/Rakefile +31 -0
- data/descope.gemspec +34 -0
- data/examples/ruby/Gemfile +4 -0
- data/examples/ruby/Gemfile.lock +41 -0
- data/examples/ruby/access_key_app.rb +45 -0
- data/examples/ruby/enchantedlink_app.rb +65 -0
- data/examples/ruby/magiclink_app.rb +81 -0
- data/examples/ruby/management/Gemfile +5 -0
- data/examples/ruby/management/Gemfile.lock +38 -0
- data/examples/ruby/management/access_key_app.rb +71 -0
- data/examples/ruby/management/audit_app.rb +25 -0
- data/examples/ruby/management/authz_app.rb +135 -0
- data/examples/ruby/management/authz_files.json +229 -0
- data/examples/ruby/management/flow_app.rb +57 -0
- data/examples/ruby/management/permission_app.rb +56 -0
- data/examples/ruby/management/role_app.rb +58 -0
- data/examples/ruby/management/tenant_app.rb +60 -0
- data/examples/ruby/management/user_app.rb +60 -0
- data/examples/ruby/oauth_app.rb +39 -0
- data/examples/ruby/otp_app.rb +50 -0
- data/examples/ruby/password_app.rb +76 -0
- data/examples/ruby/saml_app.rb +38 -0
- data/examples/ruby-on-rails-api/descope/.dockerignore +37 -0
- data/examples/ruby-on-rails-api/descope/.gitattributes +9 -0
- data/examples/ruby-on-rails-api/descope/.gitignore +40 -0
- data/examples/ruby-on-rails-api/descope/.node-version +1 -0
- data/examples/ruby-on-rails-api/descope/.ruby-version +1 -0
- data/examples/ruby-on-rails-api/descope/Dockerfile +75 -0
- data/examples/ruby-on-rails-api/descope/Gemfile +67 -0
- data/examples/ruby-on-rails-api/descope/Gemfile.lock +284 -0
- data/examples/ruby-on-rails-api/descope/Procfile.dev +3 -0
- data/examples/ruby-on-rails-api/descope/README.md +54 -0
- data/examples/ruby-on-rails-api/descope/Rakefile +6 -0
- data/examples/ruby-on-rails-api/descope/app/assets/builds/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/app/assets/config/manifest.js +3 -0
- data/examples/ruby-on-rails-api/descope/app/assets/images/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/app/assets/images/descope.jpeg +0 -0
- data/examples/ruby-on-rails-api/descope/app/assets/images/favicon.ico +0 -0
- data/examples/ruby-on-rails-api/descope/app/assets/images/logo192.png +0 -0
- data/examples/ruby-on-rails-api/descope/app/assets/images/logo512.png +0 -0
- data/examples/ruby-on-rails-api/descope/app/assets/stylesheets/application.bootstrap.scss +67 -0
- data/examples/ruby-on-rails-api/descope/app/channels/application_cable/channel.rb +4 -0
- data/examples/ruby-on-rails-api/descope/app/channels/application_cable/connection.rb +4 -0
- data/examples/ruby-on-rails-api/descope/app/controllers/application_controller.rb +2 -0
- data/examples/ruby-on-rails-api/descope/app/controllers/concerns/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/app/controllers/homepage_controller.rb +4 -0
- data/examples/ruby-on-rails-api/descope/app/controllers/session_controller.rb +66 -0
- data/examples/ruby-on-rails-api/descope/app/helpers/application_helper.rb +2 -0
- data/examples/ruby-on-rails-api/descope/app/helpers/homepage_helper.rb +2 -0
- data/examples/ruby-on-rails-api/descope/app/helpers/session_helper.rb +2 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/App.css +53 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/application.js +5 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/components/App.jsx +4 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/components/Dashboard.jsx +60 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/components/Home.jsx +27 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/components/Login.jsx +45 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/components/Profile.jsx +81 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/components/index.html +11 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/components/index.jsx +24 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/controllers/application.js +9 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/controllers/index.js +5 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/reportWebVitals.js +13 -0
- data/examples/ruby-on-rails-api/descope/app/javascript/routes/index.jsx +17 -0
- data/examples/ruby-on-rails-api/descope/app/jobs/application_job.rb +7 -0
- data/examples/ruby-on-rails-api/descope/app/mailers/application_mailer.rb +4 -0
- data/examples/ruby-on-rails-api/descope/app/models/application_record.rb +3 -0
- data/examples/ruby-on-rails-api/descope/app/models/concerns/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/app/views/homepage/index.html.erb +2 -0
- data/examples/ruby-on-rails-api/descope/app/views/layouts/application.html.erb +16 -0
- data/examples/ruby-on-rails-api/descope/app/views/layouts/mailer.html.erb +13 -0
- data/examples/ruby-on-rails-api/descope/app/views/layouts/mailer.text.erb +1 -0
- data/examples/ruby-on-rails-api/descope/app/views/session/index.html.erb +2 -0
- data/examples/ruby-on-rails-api/descope/bin/bundle +109 -0
- data/examples/ruby-on-rails-api/descope/bin/dev +11 -0
- data/examples/ruby-on-rails-api/descope/bin/docker-entrypoint +8 -0
- data/examples/ruby-on-rails-api/descope/bin/rails +4 -0
- data/examples/ruby-on-rails-api/descope/bin/rake +4 -0
- data/examples/ruby-on-rails-api/descope/bin/setup +36 -0
- data/examples/ruby-on-rails-api/descope/build.js +30 -0
- data/examples/ruby-on-rails-api/descope/config/application.rb +42 -0
- data/examples/ruby-on-rails-api/descope/config/boot.rb +4 -0
- data/examples/ruby-on-rails-api/descope/config/cable.yml +10 -0
- data/examples/ruby-on-rails-api/descope/config/config.yml +9 -0
- data/examples/ruby-on-rails-api/descope/config/credentials.yml.enc +1 -0
- data/examples/ruby-on-rails-api/descope/config/database.yml +25 -0
- data/examples/ruby-on-rails-api/descope/config/environment.rb +5 -0
- data/examples/ruby-on-rails-api/descope/config/environments/development.rb +76 -0
- data/examples/ruby-on-rails-api/descope/config/environments/production.rb +97 -0
- data/examples/ruby-on-rails-api/descope/config/environments/test.rb +64 -0
- data/examples/ruby-on-rails-api/descope/config/initializers/assets.rb +13 -0
- data/examples/ruby-on-rails-api/descope/config/initializers/content_security_policy.rb +25 -0
- data/examples/ruby-on-rails-api/descope/config/initializers/filter_parameter_logging.rb +8 -0
- data/examples/ruby-on-rails-api/descope/config/initializers/inflections.rb +16 -0
- data/examples/ruby-on-rails-api/descope/config/initializers/load_config.rb +12 -0
- data/examples/ruby-on-rails-api/descope/config/initializers/permissions_policy.rb +13 -0
- data/examples/ruby-on-rails-api/descope/config/locales/en.yml +31 -0
- data/examples/ruby-on-rails-api/descope/config/puma.rb +35 -0
- data/examples/ruby-on-rails-api/descope/config/routes.rb +18 -0
- data/examples/ruby-on-rails-api/descope/config/storage.yml +34 -0
- data/examples/ruby-on-rails-api/descope/config.ru +6 -0
- data/examples/ruby-on-rails-api/descope/db/seeds.rb +9 -0
- data/examples/ruby-on-rails-api/descope/lib/assets/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/lib/tasks/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/log/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/package-lock.json +19680 -0
- data/examples/ruby-on-rails-api/descope/package.json +51 -0
- data/examples/ruby-on-rails-api/descope/public/404.html +67 -0
- data/examples/ruby-on-rails-api/descope/public/422.html +67 -0
- data/examples/ruby-on-rails-api/descope/public/500.html +66 -0
- data/examples/ruby-on-rails-api/descope/public/apple-touch-icon-precomposed.png +0 -0
- data/examples/ruby-on-rails-api/descope/public/apple-touch-icon.png +0 -0
- data/examples/ruby-on-rails-api/descope/public/favicon.ico +0 -0
- data/examples/ruby-on-rails-api/descope/public/robots.txt +1 -0
- data/examples/ruby-on-rails-api/descope/storage/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/tmp/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/tmp/pids/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/tmp/storage/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/vendor/.keep +0 -0
- data/examples/ruby-on-rails-api/descope/yarn.lock +10780 -0
- data/lib/descope/api/v1/auth/enchantedlink.rb +156 -0
- data/lib/descope/api/v1/auth/magiclink.rb +170 -0
- data/lib/descope/api/v1/auth/oauth.rb +72 -0
- data/lib/descope/api/v1/auth/otp.rb +186 -0
- data/lib/descope/api/v1/auth/password.rb +100 -0
- data/lib/descope/api/v1/auth/saml.rb +48 -0
- data/lib/descope/api/v1/auth/totp.rb +72 -0
- data/lib/descope/api/v1/auth.rb +452 -0
- data/lib/descope/api/v1/management/access_key.rb +81 -0
- data/lib/descope/api/v1/management/audit.rb +82 -0
- data/lib/descope/api/v1/management/authz.rb +165 -0
- data/lib/descope/api/v1/management/common.rb +147 -0
- data/lib/descope/api/v1/management/flow.rb +55 -0
- data/lib/descope/api/v1/management/password.rb +58 -0
- data/lib/descope/api/v1/management/permission.rb +48 -0
- data/lib/descope/api/v1/management/project.rb +53 -0
- data/lib/descope/api/v1/management/role.rb +48 -0
- data/lib/descope/api/v1/management/scim.rb +206 -0
- data/lib/descope/api/v1/management/sso_settings.rb +153 -0
- data/lib/descope/api/v1/management/tenant.rb +71 -0
- data/lib/descope/api/v1/management/user.rb +619 -0
- data/lib/descope/api/v1/management.rb +38 -0
- data/lib/descope/api/v1/session.rb +84 -0
- data/lib/descope/api/v1.rb +13 -0
- data/lib/descope/client.rb +6 -0
- data/lib/descope/exception.rb +50 -0
- data/lib/descope/mixins/common.rb +129 -0
- data/lib/descope/mixins/headers.rb +15 -0
- data/lib/descope/mixins/http.rb +133 -0
- data/lib/descope/mixins/initializer.rb +80 -0
- data/lib/descope/mixins/logging.rb +30 -0
- data/lib/descope/mixins/validation.rb +79 -0
- data/lib/descope/mixins.rb +22 -0
- data/lib/descope/version.rb +7 -0
- data/lib/descope.rb +9 -0
- data/lib/descope_client.rb +5 -0
- data/release-please-config.json +18 -0
- data/renovate.json +6 -0
- data/spec/factories/user.rb +16 -0
- data/spec/lib.descope/api/v1/auth/enchantedlink_spec.rb +159 -0
- data/spec/lib.descope/api/v1/auth/magiclink_spec.rb +282 -0
- data/spec/lib.descope/api/v1/auth/oauth_spec.rb +117 -0
- data/spec/lib.descope/api/v1/auth/otp_spec.rb +285 -0
- data/spec/lib.descope/api/v1/auth/password_spec.rb +124 -0
- data/spec/lib.descope/api/v1/auth/saml_spec.rb +55 -0
- data/spec/lib.descope/api/v1/auth/totp_spec.rb +70 -0
- data/spec/lib.descope/api/v1/auth_spec.rb +372 -0
- data/spec/lib.descope/api/v1/management/access_key_spec.rb +118 -0
- data/spec/lib.descope/api/v1/management/audit_spec.rb +78 -0
- data/spec/lib.descope/api/v1/management/authz_spec.rb +336 -0
- data/spec/lib.descope/api/v1/management/flow_spec.rb +78 -0
- data/spec/lib.descope/api/v1/management/password_spec.rb +25 -0
- data/spec/lib.descope/api/v1/management/permission_spec.rb +81 -0
- data/spec/lib.descope/api/v1/management/project_spec.rb +63 -0
- data/spec/lib.descope/api/v1/management/role_spec.rb +85 -0
- data/spec/lib.descope/api/v1/management/scim_spec.rb +312 -0
- data/spec/lib.descope/api/v1/management/sso_settings_spec.rb +172 -0
- data/spec/lib.descope/api/v1/management/tenant_spec.rb +141 -0
- data/spec/lib.descope/api/v1/management/user_spec.rb +667 -0
- data/spec/lib.descope/api/v1/session_spec.rb +117 -0
- data/spec/lib.descope/client_spec.rb +40 -0
- data/spec/spec_helper.rb +72 -0
- data/spec/support/client_config.rb +14 -0
- data/spec/support/dummy_class.rb +36 -0
- data/spec/support/utils.rb +32 -0
- metadata +420 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cgi'
|
|
4
|
+
|
|
5
|
+
module Descope
|
|
6
|
+
module Api
|
|
7
|
+
module V1
|
|
8
|
+
module Auth
|
|
9
|
+
# Holds all the password API calls
|
|
10
|
+
module SAML
|
|
11
|
+
include Descope::Mixins::Validation
|
|
12
|
+
include Descope::Mixins::Common::EndpointsV1
|
|
13
|
+
include Descope::Mixins::Common::EndpointsV2
|
|
14
|
+
|
|
15
|
+
# rubocop:disable Metrics/AbcSize
|
|
16
|
+
def saml_sign_in(tenant: nil, redirect_url: nil, prompt: nil, stepup: false,
|
|
17
|
+
mfa: false, custom_claims: {}, sso_app_id: nil)
|
|
18
|
+
validate_tenant(tenant)
|
|
19
|
+
validate_redirect_url(redirect_url)
|
|
20
|
+
uri = compose_saml_signin_url(tenant, redirect_url, prompt)
|
|
21
|
+
|
|
22
|
+
request_params = {}
|
|
23
|
+
request_params[:stepup] = stepup
|
|
24
|
+
request_params[:mfa] = mfa
|
|
25
|
+
request_params[:customClaims] = custom_claims
|
|
26
|
+
request_params[:ssoAppId] = sso_app_id unless sso_app_id.nil?
|
|
27
|
+
|
|
28
|
+
post(uri, request_params)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def saml_exchange_token(code = nil)
|
|
32
|
+
exchange_token(SAML_EXCHANGE_TOKEN_PATH, code)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def compose_saml_signin_url(tenant, redirect_url, prompt)
|
|
38
|
+
uri = AUTH_SAML_START_PATH
|
|
39
|
+
uri += "?tenant=#{CGI.escape(tenant)}" unless tenant.nil?
|
|
40
|
+
uri += "&redirectUrl=#{CGI.escape(redirect_url)}" unless redirect_url.nil?
|
|
41
|
+
uri += "&prompt=#{CGI.escape(prompt)}" unless prompt.nil?
|
|
42
|
+
uri
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Descope
|
|
4
|
+
module Api
|
|
5
|
+
module V1
|
|
6
|
+
module Auth
|
|
7
|
+
# Holds all the password API calls
|
|
8
|
+
module TOTP
|
|
9
|
+
include Descope::Mixins::Validation
|
|
10
|
+
include Descope::Mixins::Common::EndpointsV1
|
|
11
|
+
include Descope::Mixins::Common::EndpointsV2
|
|
12
|
+
|
|
13
|
+
def totp_sign_in_code(login_id: nil, login_options: nil, code: nil)
|
|
14
|
+
# Sign in by verifying the validity of a TOTP code entered by an end user.
|
|
15
|
+
validate_login_id(login_id)
|
|
16
|
+
validate_code(code)
|
|
17
|
+
uri = VERIFY_TOTP_PATH
|
|
18
|
+
body = totp_compose_signin_body(login_id, code, login_options)
|
|
19
|
+
res = post(uri, body, {}, nil)
|
|
20
|
+
generate_jwt_response(response_body: res, refresh_cookie: res.fetch('refreshJwt', {}))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def totp_sign_up(login_id: nil, user: nil, sso_app_id: nil)
|
|
24
|
+
# Sign up (create) a new user using their email or phone number.
|
|
25
|
+
# (optional) Include additional user metadata that you wish to save.
|
|
26
|
+
user ||= {}
|
|
27
|
+
validate_login_id(login_id)
|
|
28
|
+
|
|
29
|
+
request_params = {
|
|
30
|
+
loginId: login_id
|
|
31
|
+
}
|
|
32
|
+
request_params[:user] = user_compose_update_body(**user) unless user.empty?
|
|
33
|
+
request_params[:ssoAppId] = sso_app_id unless sso_app_id.nil?
|
|
34
|
+
post(SIGN_UP_AUTH_TOTP_PATH, request_params)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def totp_add_update_key(login_id: nil, refresh_token: nil)
|
|
38
|
+
# Add or update TOTP key for existing end userUpdate the email address of an end user,
|
|
39
|
+
# after verifying the authenticity of the end user using OTP.
|
|
40
|
+
validate_login_id(login_id)
|
|
41
|
+
post(UPDATE_TOTP_PATH, { loginId: login_id }, {}, refresh_token)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# rubocop:disable Metrics/MethodLength
|
|
47
|
+
def totp_compose_signin_body(login_id, code, login_options)
|
|
48
|
+
login_options ||= {}
|
|
49
|
+
unless login_options.is_a?(Hash)
|
|
50
|
+
raise Descope::ArgumentException.new(
|
|
51
|
+
'Unable to read login_option, not a Hash',
|
|
52
|
+
code: 400
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
body = {
|
|
57
|
+
loginId: login_id,
|
|
58
|
+
code:,
|
|
59
|
+
loginOptions: {}
|
|
60
|
+
}
|
|
61
|
+
body[:loginOptions][:stepup] = login_options.fetch(:stepup, false)
|
|
62
|
+
body[:loginOptions][:mfa] = login_options.fetch(:mfa, false)
|
|
63
|
+
body[:loginOptions][:customClaims] = login_options.fetch(:custom_claims, {})
|
|
64
|
+
body[:loginOptions][:ssoAppId] = login_options.fetch(:sso_app_id, nil)
|
|
65
|
+
|
|
66
|
+
body
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'descope/mixins/common'
|
|
4
|
+
require 'descope/api/v1/auth/password'
|
|
5
|
+
require 'descope/api/v1/auth/enchantedlink'
|
|
6
|
+
require 'descope/api/v1/auth/magiclink'
|
|
7
|
+
require 'descope/api/v1/auth/oauth'
|
|
8
|
+
require 'descope/api/v1/auth/otp'
|
|
9
|
+
require 'descope/api/v1/auth/saml'
|
|
10
|
+
require 'descope/api/v1/auth/totp'
|
|
11
|
+
|
|
12
|
+
module Descope
|
|
13
|
+
module Api
|
|
14
|
+
module V1
|
|
15
|
+
# Holds all the management API calls
|
|
16
|
+
module Auth
|
|
17
|
+
include Descope::Mixins::Common
|
|
18
|
+
include Descope::Mixins::Common::EndpointsV1
|
|
19
|
+
include Descope::Mixins::Common::EndpointsV2
|
|
20
|
+
include Descope::Api::V1::Auth::Password
|
|
21
|
+
include Descope::Api::V1::Auth::EnchantedLink
|
|
22
|
+
include Descope::Api::V1::Auth::MagicLink
|
|
23
|
+
include Descope::Api::V1::Auth::OAuth
|
|
24
|
+
include Descope::Api::V1::Auth::OTP
|
|
25
|
+
include Descope::Api::V1::Auth::SAML
|
|
26
|
+
include Descope::Api::V1::Auth::TOTP
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
ALGORITHM_KEY = 'alg'
|
|
30
|
+
|
|
31
|
+
def generate_jwt_response(response_body: nil, refresh_cookie: nil, audience: nil)
|
|
32
|
+
if response_body.nil? || response_body.empty?
|
|
33
|
+
raise AuthException.new('Unable to generate jwt response. Response body is empty', code: 500)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
jwt_response = generate_auth_info(response_body, refresh_cookie, true, audience)
|
|
37
|
+
@logger.debug "jwt_response: #{jwt_response}"
|
|
38
|
+
jwt_response['user'] = response_body.key?('user') ? response_body['user'] : {}
|
|
39
|
+
jwt_response['firstSeen'] = response_body.key?('firstSeen') ? response_body['firstSeen'] : true
|
|
40
|
+
|
|
41
|
+
jwt_response
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def exchange_access_key(access_key = nil)
|
|
45
|
+
post(EXCHANGE_AUTH_ACCESS_KEY_PATH, {}, {}, access_key)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def select_tenant(tenant_id: nil, refresh_token: nil)
|
|
49
|
+
validate_refresh_token_not_nil(refresh_token)
|
|
50
|
+
res = post(SELECT_TENANT_PATH, { tenantId: tenant_id }, {}, refresh_token)
|
|
51
|
+
@logger.debug "select_tenant response: #{res}"
|
|
52
|
+
generate_jwt_response(
|
|
53
|
+
response_body: res,
|
|
54
|
+
refresh_cookie: res['refreshJwt']
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def validate_permissions(jwt_response: nil, permissions: nil)
|
|
59
|
+
# Validate that a jwt_response has been granted the specified permissions.
|
|
60
|
+
# For a multi-tenant environment use validate_tenant_permissions function
|
|
61
|
+
validate_tenant_permissions(jwt_response:, permissions:)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/MethodLength
|
|
65
|
+
def validate_tenant_permissions(jwt_response: nil, tenant: nil, permissions: nil)
|
|
66
|
+
# Validate that a jwt_response has been granted the specified permissions on the specified tenant.
|
|
67
|
+
# For a multi-tenant environment use validate_tenant_permissions function
|
|
68
|
+
if permissions.is_a?(String)
|
|
69
|
+
permissions = [permissions]
|
|
70
|
+
else
|
|
71
|
+
permissions ||= []
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
unless jwt_response.is_a?(Hash)
|
|
75
|
+
raise Descope::ArgumentException.new(
|
|
76
|
+
'Invalid JWT response hash', code: 400
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
return false unless jwt_response
|
|
81
|
+
|
|
82
|
+
granted_permissions = if tenant.nil? || tenant.to_s.empty?
|
|
83
|
+
jwt_response.fetch('permissions', [])
|
|
84
|
+
else
|
|
85
|
+
# ensure that the tenant is associated with the jwt_response
|
|
86
|
+
@logger.debug "tenant associated jwt: #{jwt_response['tenants']&.key?(tenant)}"
|
|
87
|
+
return false unless jwt_response['tenants'].key?(tenant)
|
|
88
|
+
|
|
89
|
+
# dig is a method in Ruby for safely navigating nested data structures like hashes
|
|
90
|
+
# and arrays. It allows you to access deeply nested values without worrying about
|
|
91
|
+
# raising an error if a middle value is nil.
|
|
92
|
+
tenant_permission = jwt_response.dig('tenants', tenant, 'permissions') || []
|
|
93
|
+
tenant_permission = [] if tenant_permission.nil?
|
|
94
|
+
if tenant_permission.is_a?(String)
|
|
95
|
+
@logger.debug "tenant_permission string: #{tenant_permission}"
|
|
96
|
+
[tenant_permission]
|
|
97
|
+
else
|
|
98
|
+
@logger.debug "tenant_permission array: #{tenant_permission}"
|
|
99
|
+
tenant_permission
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Validate all permissions are granted
|
|
104
|
+
permissions.all? do |permission|
|
|
105
|
+
granted_permissions.include?(permission)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def validate_roles(jwt_response: nil, roles: nil)
|
|
110
|
+
# Validate that a jwt_response has been granted the specified roles.
|
|
111
|
+
# For a multi-tenant environment use validate_tenant_roles function
|
|
112
|
+
validate_tenant_roles(jwt_response:, tenant: '', roles:)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def validate_tenant_roles(jwt_response: nil, tenant: nil, roles: nil)
|
|
116
|
+
# Validate that a jwt_response has been granted the specified roles on the specified tenant.
|
|
117
|
+
# For a multi-tenant environment use validate_tenant_roles function
|
|
118
|
+
@logger.debug "Validate_tenant_roles: #{jwt_response}, #{tenant}, #{roles}"
|
|
119
|
+
if roles.is_a?(String)
|
|
120
|
+
roles = [roles]
|
|
121
|
+
else
|
|
122
|
+
roles ||= []
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
unless jwt_response.is_a?(Hash)
|
|
126
|
+
raise Descope::ArgumentException.new(
|
|
127
|
+
'Invalid JWT response hash', code: 400
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
return false unless jwt_response
|
|
132
|
+
|
|
133
|
+
granted_roles = if tenant.nil? || tenant.to_s.empty?
|
|
134
|
+
jwt_response.fetch('roles', [])
|
|
135
|
+
else
|
|
136
|
+
# ensure that the tenant is associated with the jwt_response
|
|
137
|
+
return false unless jwt_response['tenants'].key?(tenant)
|
|
138
|
+
|
|
139
|
+
# dig is a method in Ruby for safely navigating nested data structures like hashes
|
|
140
|
+
# and arrays. It allows you to access deeply nested values without worrying about
|
|
141
|
+
# raising an error if a middle value is nil.
|
|
142
|
+
tenant_roles = jwt_response.dig('tenants', tenant, 'roles') || []
|
|
143
|
+
tenant_roles = [] if tenant_roles.nil?
|
|
144
|
+
if tenant_roles.is_a?(String)
|
|
145
|
+
[tenant_roles]
|
|
146
|
+
else
|
|
147
|
+
tenant_roles
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
@logger.debug "granted_roles: #{granted_roles}"
|
|
152
|
+
# Validate all roles are granted
|
|
153
|
+
roles.all? do |role|
|
|
154
|
+
@logger.debug "granted_roles.include?(#{role}): #{granted_roles.include?(role)}"
|
|
155
|
+
granted_roles.include?(role)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def validate_token(token, _audience = nil)
|
|
160
|
+
@logger.debug "validating token: #{token}"
|
|
161
|
+
raise AuthException.new('Token validation received empty token', code: 500) if token.nil? || token.to_s.empty?
|
|
162
|
+
|
|
163
|
+
unverified_header = jwt_get_unverified_header(token)
|
|
164
|
+
@logger.debug "unverified_header: #{unverified_header}"
|
|
165
|
+
alg_header = unverified_header[ALGORITHM_KEY]
|
|
166
|
+
@logger.debug "alg_header: #{alg_header}"
|
|
167
|
+
|
|
168
|
+
if alg_header.nil? || alg_header == 'none'
|
|
169
|
+
raise AuthException.new('Token header is missing property: alg', code: 500)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
kid = unverified_header['kid']
|
|
173
|
+
@logger.debug "kid: #{kid}"
|
|
174
|
+
raise AuthException.new('Token header is missing property: kid', code: 500) if kid.nil?
|
|
175
|
+
|
|
176
|
+
found_key = nil
|
|
177
|
+
@mlock.synchronize do
|
|
178
|
+
if @public_keys.nil? || @public_keys == {} || @public_keys.to_s.empty? || @public_keys[kid].nil?
|
|
179
|
+
@logger.debug 'fetching public keys'
|
|
180
|
+
# fetch keys from /v2/keys and set them in @public_keys
|
|
181
|
+
fetch_public_keys
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
found_key = @public_keys[kid]
|
|
185
|
+
@logger.debug "found_key: #{found_key}"
|
|
186
|
+
raise AuthException.new('Unable to validate public key. Public key not found.', code: 500) if found_key.nil?
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# save reference to the found key
|
|
190
|
+
# (as another thread can change the self.public_keys hash)
|
|
191
|
+
@logger.debug 'checking if alg_header matches alg_from_key'
|
|
192
|
+
alg_from_key = found_key[1]
|
|
193
|
+
if alg_header != alg_from_key
|
|
194
|
+
raise AuthException.new(
|
|
195
|
+
'Algorithm signature in JWT header does not match the algorithm signature in the Public key.',
|
|
196
|
+
code: 500
|
|
197
|
+
)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
begin
|
|
201
|
+
@logger.debug 'decoding token'
|
|
202
|
+
claims = JWT.decode(
|
|
203
|
+
token,
|
|
204
|
+
found_key[0].public_key,
|
|
205
|
+
true,
|
|
206
|
+
{ algorithm: alg_header, exp_leeway: @jwt_validation_leeway }
|
|
207
|
+
)[0] # the payload is the first index in the decoded array
|
|
208
|
+
rescue JWT::ExpiredSignature => e
|
|
209
|
+
raise AuthException.new(
|
|
210
|
+
"Received Invalid token times error due to time glitch (between machines) during jwt validation, try to set the jwt_validation_leeway parameter (in DescopeClient) to higher value than 5sec which is the default: #{e.message}", code: 500
|
|
211
|
+
)
|
|
212
|
+
end
|
|
213
|
+
claims['jwt'] = token
|
|
214
|
+
@logger.debug "claims: #{claims}"
|
|
215
|
+
claims
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
private
|
|
219
|
+
|
|
220
|
+
def generate_auth_info(response_body, refresh_token, user_jwt, audience = nil)
|
|
221
|
+
@logger.debug "generating auth info: #{response_body}, #{refresh_token}, #{user_jwt}, #{audience}"
|
|
222
|
+
jwt_response = {}
|
|
223
|
+
|
|
224
|
+
# validate the session token if sessionJwt is not empty
|
|
225
|
+
st_jwt = response_body.fetch('sessionJwt', '')
|
|
226
|
+
if st_jwt
|
|
227
|
+
jwt_response[SESSION_TOKEN_NAME] = validate_token(st_jwt, audience)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# validate refresh token if refresh_token was passed or if refreshJwt is not empty
|
|
231
|
+
rt_jwt = response_body.fetch('refreshJwt', '')
|
|
232
|
+
|
|
233
|
+
if refresh_token
|
|
234
|
+
jwt_response[REFRESH_SESSION_TOKEN_NAME] = validate_token(refresh_token, audience)
|
|
235
|
+
elsif rt_jwt
|
|
236
|
+
jwt_response[REFRESH_SESSION_TOKEN_NAME] = validate_token(rt_jwt, audience)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
jwt_response = adjust_properties(jwt_response, user_jwt)
|
|
240
|
+
|
|
241
|
+
if user_jwt
|
|
242
|
+
jwt_response[COOKIE_DATA_NAME] = {
|
|
243
|
+
exp: response_body.fetch('cookieExpiration', 0),
|
|
244
|
+
maxAge: response_body.fetch('cookieMaxAge', 0),
|
|
245
|
+
domain: response_body.fetch('cookieDomain', ''),
|
|
246
|
+
path: response_body.fetch('cookiePath', '/')
|
|
247
|
+
}
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
jwt_response
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def adjust_properties(jwt_response, user_jwt)
|
|
254
|
+
# Save permissions, roles and tenants info from Session token or from refresh token on the json top level
|
|
255
|
+
if jwt_response[SESSION_TOKEN_NAME]
|
|
256
|
+
jwt_response['permissions'] = jwt_response[SESSION_TOKEN_NAME].fetch('permissions', [])
|
|
257
|
+
jwt_response['roles'] = jwt_response[SESSION_TOKEN_NAME].fetch('roles', [])
|
|
258
|
+
jwt_response['tenants'] = jwt_response[SESSION_TOKEN_NAME].fetch('tenants', {})
|
|
259
|
+
elsif jwt_response[REFRESH_SESSION_TOKEN_NAME]
|
|
260
|
+
jwt_response['permissions'] = jwt_response[REFRESH_SESSION_TOKEN_NAME].fetch('permissions', [])
|
|
261
|
+
jwt_response['roles'] = jwt_response[REFRESH_SESSION_TOKEN_NAME].fetch('roles', [])
|
|
262
|
+
jwt_response['tenants'] = jwt_response[REFRESH_SESSION_TOKEN_NAME].fetch('tenants', {})
|
|
263
|
+
else
|
|
264
|
+
jwt_response['permissions'] = jwt_response.fetch('permissions', [])
|
|
265
|
+
jwt_response['roles'] = jwt_response.fetch('roles', [])
|
|
266
|
+
jwt_response['tenants'] = jwt_response.fetch('tenants', {})
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Save the projectID also in the dict top level
|
|
270
|
+
issuer =
|
|
271
|
+
jwt_response.fetch(SESSION_TOKEN_NAME, {}).fetch('iss', nil) ||
|
|
272
|
+
jwt_response.fetch(REFRESH_SESSION_TOKEN_NAME, {}).fetch('iss', nil) ||
|
|
273
|
+
jwt_response.fetch('iss', '')
|
|
274
|
+
|
|
275
|
+
jwt_response['projectId'] = issuer.split('/').last # support both url issuer and project ID issuer
|
|
276
|
+
|
|
277
|
+
sub =
|
|
278
|
+
jwt_response.fetch(SESSION_TOKEN_NAME, {}).fetch('iss', nil) ||
|
|
279
|
+
jwt_response.fetch(REFRESH_SESSION_TOKEN_NAME, {}).fetch('iss', nil) ||
|
|
280
|
+
jwt_response.fetch('sub', '')
|
|
281
|
+
|
|
282
|
+
if user_jwt
|
|
283
|
+
jwt_response['userId'] = sub # Save the userID also in the dict top level
|
|
284
|
+
else
|
|
285
|
+
jwt_response['keyId'] = sub # Save the AccessKeyID also in the dict top level
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
jwt_response
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def jwt_get_unverified_header(token)
|
|
292
|
+
begin
|
|
293
|
+
decode_response = JWT.decode(token, nil, false)
|
|
294
|
+
rescue JWT::DecodeError => e
|
|
295
|
+
raise AuthException.new("Unable to parse token. #{e.message}", code: 500)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# The JWT.decode method returns an array where
|
|
299
|
+
# the first element is the payload and the second element is the header.
|
|
300
|
+
decode_response[1]
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def fetch_public_keys
|
|
304
|
+
response = token_validation_key(@project_id)
|
|
305
|
+
unless response.is_a?(Hash) && response.key?('keys')
|
|
306
|
+
raise AuthException.new("Unable to fetch public keys. #{response}", code: 500)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
jwkeys_wrapper = response
|
|
310
|
+
jwkeys = jwkeys_wrapper['keys']
|
|
311
|
+
@public_keys = {}
|
|
312
|
+
|
|
313
|
+
jwkeys.each do |key|
|
|
314
|
+
loaded_kid, pub_key, alg = validate_and_load_public_key(key)
|
|
315
|
+
@public_keys[loaded_kid] = [pub_key, alg]
|
|
316
|
+
rescue AuthException
|
|
317
|
+
nil
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
322
|
+
def validate_and_load_public_key(public_key)
|
|
323
|
+
unless public_key.is_a?(String) || public_key.is_a?(Hash)
|
|
324
|
+
raise AuthException.new(
|
|
325
|
+
'Unable to load public key. Invalid public key error: (unknown type)',
|
|
326
|
+
code: 500
|
|
327
|
+
)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
if public_key.is_a? String
|
|
331
|
+
begin
|
|
332
|
+
public_key = JSON.parse(public_key)
|
|
333
|
+
rescue JSON::ParserError => e
|
|
334
|
+
raise AuthException.new(
|
|
335
|
+
"Unable to parse public key json, error: #{e.message}",
|
|
336
|
+
code: 500
|
|
337
|
+
)
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
alg = public_key[ALGORITHM_KEY]
|
|
342
|
+
if alg.nil?
|
|
343
|
+
raise AuthException.new(
|
|
344
|
+
'Unable to load public key. Missing property: alg',
|
|
345
|
+
code: 500
|
|
346
|
+
)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
kid = public_key['kid']
|
|
350
|
+
if kid.nil?
|
|
351
|
+
raise AuthException.new(
|
|
352
|
+
'Unable to load public key. Missing property: kid',
|
|
353
|
+
code: 500
|
|
354
|
+
)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
begin
|
|
358
|
+
# Load and validate public key
|
|
359
|
+
[kid, JWT::JWK.new(public_key), alg]
|
|
360
|
+
rescue JWT::JWKError => e
|
|
361
|
+
raise AuthException.new(
|
|
362
|
+
"Unable to load public key #{e.message}",
|
|
363
|
+
code: 500
|
|
364
|
+
)
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def validate_refresh_token_provided(login_options, refresh_token)
|
|
369
|
+
refresh_required = !login_options.nil? && (login_options[:mfa] || login_options[:stepup])
|
|
370
|
+
refresh_missing = refresh_token.nil? || refresh_token.to_s.empty?
|
|
371
|
+
|
|
372
|
+
return unless refresh_required && refresh_missing
|
|
373
|
+
|
|
374
|
+
raise AuthException.new(
|
|
375
|
+
'Missing refresh token for stepup/mfa',
|
|
376
|
+
code: 400
|
|
377
|
+
)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def compose_url(base, method)
|
|
381
|
+
suffix = get_method_string(method)
|
|
382
|
+
unless suffix
|
|
383
|
+
raise AuthException.new(
|
|
384
|
+
"Unable to compose url. Unknown delivery method: #{method}",
|
|
385
|
+
code: 500
|
|
386
|
+
)
|
|
387
|
+
end
|
|
388
|
+
"#{base}/#{suffix}"
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def get_login_id_by_method(method: nil, user: {})
|
|
392
|
+
login_id = {
|
|
393
|
+
DeliveryMethod::WHATSAPP => ['whatsapp', user.fetch(:phone, '')],
|
|
394
|
+
DeliveryMethod::SMS => ['phone', user.fetch(:phone, '')],
|
|
395
|
+
DeliveryMethod::EMAIL => ['email', user.fetch(:email, '')]
|
|
396
|
+
}[method]
|
|
397
|
+
|
|
398
|
+
raise AuthException.new("Unknown delivery method: #{method}", code: 400) if login_id.nil?
|
|
399
|
+
|
|
400
|
+
login_id
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def adjust_and_verify_delivery_method(method, login_id, user)
|
|
404
|
+
return false if login_id.nil?
|
|
405
|
+
|
|
406
|
+
return false unless user.is_a?(Hash)
|
|
407
|
+
|
|
408
|
+
case method
|
|
409
|
+
when DeliveryMethod::EMAIL
|
|
410
|
+
user[:email] ||= login_id
|
|
411
|
+
begin
|
|
412
|
+
validate_email(user[:email])
|
|
413
|
+
return true
|
|
414
|
+
rescue AuthException
|
|
415
|
+
return false
|
|
416
|
+
end
|
|
417
|
+
when DeliveryMethod::SMS
|
|
418
|
+
user[:phone] ||= login_id
|
|
419
|
+
return false unless /^#{PHONE_REGEX}$/.match(user[:phone])
|
|
420
|
+
when DeliveryMethod::WHATSAPP
|
|
421
|
+
user[:phone] ||= login_id
|
|
422
|
+
return false unless /^#{PHONE_REGEX}$/.match(user[:phone])
|
|
423
|
+
else
|
|
424
|
+
return false
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
true
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def extract_masked_address(response, method)
|
|
431
|
+
if [DeliveryMethod::SMS, DeliveryMethod::WHATSAPP].include?(method)
|
|
432
|
+
response['maskedPhone']
|
|
433
|
+
elsif method == DeliveryMethod::EMAIL
|
|
434
|
+
response['maskedEmail']
|
|
435
|
+
else
|
|
436
|
+
''
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def exchange_token(uri, code)
|
|
441
|
+
raise Descope::ArgumentException.new("Code can't be empty", code: 400) if code.nil? || code.empty?
|
|
442
|
+
|
|
443
|
+
res = post(uri, { code: })
|
|
444
|
+
generate_jwt_response(
|
|
445
|
+
response_body: res,
|
|
446
|
+
refresh_cookie: res['refreshJwt']
|
|
447
|
+
)
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Descope
|
|
4
|
+
module Api
|
|
5
|
+
module V1
|
|
6
|
+
module Management
|
|
7
|
+
# Management API calls
|
|
8
|
+
module AccessKey
|
|
9
|
+
include Descope::Mixins::Validation
|
|
10
|
+
include Descope::Api::V1::Management::Common
|
|
11
|
+
|
|
12
|
+
def create_access_key(name: nil, expire_time: nil, role_names: nil, key_tenants: nil)
|
|
13
|
+
# Create a new access key.'
|
|
14
|
+
# @see https://docs.descope.com/api/openapi/accesskeymanagement/operation/CreateAccessKey/
|
|
15
|
+
|
|
16
|
+
role_names ||= []
|
|
17
|
+
key_tenants ||= []
|
|
18
|
+
validate_tenants(key_tenants)
|
|
19
|
+
post(ACCESS_KEY_CREATE_PATH, access_key_compose_create_body(name, expire_time, role_names, key_tenants))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def access_key_compose_create_body(name, expire_time, role_names, key_tenants)
|
|
23
|
+
{
|
|
24
|
+
name:,
|
|
25
|
+
expireTime: expire_time,
|
|
26
|
+
roleNames: role_names,
|
|
27
|
+
keyTenants: associated_tenants_to_hash_array(key_tenants)
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def load_access_key(id)
|
|
32
|
+
# Load an access key.'
|
|
33
|
+
# @param id [string] The access key id.
|
|
34
|
+
# @see https://docs.descope.com/api/openapi/accesskeymanagement/operation/LoadAccessKey/
|
|
35
|
+
|
|
36
|
+
get(ACCESS_KEY_LOAD_PATH, { id: })
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def search_all_access_keys(tenant_ids = nil)
|
|
40
|
+
# Search all access keys.'
|
|
41
|
+
# @see https://docs.descope.com/api/openapi/accesskeymanagement/operation/SearchAccessKeys/
|
|
42
|
+
request_params = {
|
|
43
|
+
tenantIds: tenant_ids
|
|
44
|
+
}
|
|
45
|
+
post(ACCESS_KEYS_SEARCH_PATH, request_params)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def update_access_key(id: nil, name: nil)
|
|
49
|
+
# Update an existing access key name
|
|
50
|
+
# @see https://docs.descope.com/api/openapi/accesskeymanagement/operation/UpdateAccessKey/
|
|
51
|
+
request_params = {
|
|
52
|
+
id:,
|
|
53
|
+
name:
|
|
54
|
+
}
|
|
55
|
+
post(ACCESS_KEY_UPDATE_PATH, request_params)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def deactivate_access_key(id)
|
|
59
|
+
# Deactivate an existing access key. IMPORTANT: This deactivated key will not be usable from this stage.
|
|
60
|
+
# It will, however, persist, and can be activated again if needed.
|
|
61
|
+
# @see https://docs.descope.com/api/openapi/accesskeymanagement/operation/DeactivateAccessKey/
|
|
62
|
+
post(ACCESS_KEY_DEACTIVATE_PATH, { id: })
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def activate_access_key(id)
|
|
66
|
+
# Activate an existing access key. IMPORTANT: Only deactivated keys can be activated again,
|
|
67
|
+
# and become usable once more. New access keys are active by default.
|
|
68
|
+
# @see https://docs.descope.com/api/openapi/accesskeymanagement/operation/ActivateAccessKey/
|
|
69
|
+
post(ACCESS_KEY_ACTIVATE_PATH, { id: })
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def delete_access_key(id)
|
|
73
|
+
# Delete an existing access key. IMPORTANT: This action is irreversible. Use carefully.
|
|
74
|
+
# @see https://docs.descope.com/api/openapi/accesskeymanagement/operation/DeleteAccessKey/
|
|
75
|
+
post(ACCESS_KEY_DELETE_PATH, { id: })
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|