Module: Descope::Api::V1::Auth
- Includes:
- EnchantedLink, MagicLink, OAuth, OTP, Password, SAML, TOTP, Mixins::Common, Mixins::Common::EndpointsV1, Mixins::Common::EndpointsV2
- Included in:
- Descope::Api::V1
- Defined in:
- lib/descope/api/v1/auth.rb,
lib/descope/api/v1/auth/otp.rb,
lib/descope/api/v1/auth/saml.rb,
lib/descope/api/v1/auth/totp.rb,
lib/descope/api/v1/auth/oauth.rb,
lib/descope/api/v1/auth/password.rb,
lib/descope/api/v1/auth/magiclink.rb,
lib/descope/api/v1/auth/enchantedlink.rb
Overview
Holds all the management API calls
Defined Under Namespace
Modules: EnchantedLink, MagicLink, OAuth, OTP, Password, SAML, TOTP
Constant Summary collapse
- ALGORITHM_KEY =
'alg'
Constants included from Mixins::Common::EndpointsV2
Mixins::Common::EndpointsV2::PUBLIC_KEY_PATH
Constants included from Mixins::Common::EndpointsV1
Mixins::Common::EndpointsV1::AUTH_SAML_START_PATH, Mixins::Common::EndpointsV1::EXCHANGE_AUTH_ACCESS_KEY_PATH, Mixins::Common::EndpointsV1::GET_SESSION_ENCHANTEDLINK_AUTH_PATH, Mixins::Common::EndpointsV1::GET_SESSION_MAGICLINK_AUTH_PATH, Mixins::Common::EndpointsV1::HISTORY_PATH, Mixins::Common::EndpointsV1::LOGOUT_ALL_PATH, Mixins::Common::EndpointsV1::LOGOUT_PATH, Mixins::Common::EndpointsV1::ME_PATH, Mixins::Common::EndpointsV1::OAUTH_CREATE_REDIRECT_URL_FOR_SIGN_IN_REQUEST_PATH, Mixins::Common::EndpointsV1::OAUTH_CREATE_REDIRECT_URL_FOR_SIGN_UP_REQUEST_PATH, Mixins::Common::EndpointsV1::OAUTH_EXCHANGE_TOKEN_PATH, Mixins::Common::EndpointsV1::OAUTH_START_PATH, Mixins::Common::EndpointsV1::PASSWORD_POLICY_PATH, Mixins::Common::EndpointsV1::REFRESH_TOKEN_PATH, Mixins::Common::EndpointsV1::REPLACE_PASSWORD_PATH, Mixins::Common::EndpointsV1::SAML_EXCHANGE_TOKEN_PATH, Mixins::Common::EndpointsV1::SELECT_TENANT_PATH, Mixins::Common::EndpointsV1::SEND_RESET_PASSWORD_PATH, Mixins::Common::EndpointsV1::SIGN_IN_AUTH_ENCHANTEDLINK_PATH, Mixins::Common::EndpointsV1::SIGN_IN_AUTH_MAGICLINK_PATH, Mixins::Common::EndpointsV1::SIGN_IN_AUTH_OTP_PATH, Mixins::Common::EndpointsV1::SIGN_IN_AUTH_WEBAUTHN_FINISH_PATH, Mixins::Common::EndpointsV1::SIGN_IN_AUTH_WEBAUTHN_START_PATH, Mixins::Common::EndpointsV1::SIGN_IN_PASSWORD_PATH, Mixins::Common::EndpointsV1::SIGN_UP_AUTH_ENCHANTEDLINK_PATH, Mixins::Common::EndpointsV1::SIGN_UP_AUTH_MAGICLINK_PATH, Mixins::Common::EndpointsV1::SIGN_UP_AUTH_OTP_PATH, Mixins::Common::EndpointsV1::SIGN_UP_AUTH_TOTP_PATH, Mixins::Common::EndpointsV1::SIGN_UP_AUTH_WEBAUTHN_FINISH_PATH, Mixins::Common::EndpointsV1::SIGN_UP_AUTH_WEBAUTHN_START_PATH, Mixins::Common::EndpointsV1::SIGN_UP_OR_IN_AUTH_ENCHANTEDLINK_PATH, Mixins::Common::EndpointsV1::SIGN_UP_OR_IN_AUTH_MAGICLINK_PATH, Mixins::Common::EndpointsV1::SIGN_UP_OR_IN_AUTH_OTP_PATH, Mixins::Common::EndpointsV1::SIGN_UP_OR_IN_AUTH_WEBAUTHN_START_PATH, Mixins::Common::EndpointsV1::SIGN_UP_PASSWORD_PATH, Mixins::Common::EndpointsV1::UPDATE_AUTH_WEBAUTHN_FINISH_PATH, Mixins::Common::EndpointsV1::UPDATE_AUTH_WEBAUTHN_START_PATH, Mixins::Common::EndpointsV1::UPDATE_PASSWORD_PATH, Mixins::Common::EndpointsV1::UPDATE_TOTP_PATH, Mixins::Common::EndpointsV1::UPDATE_USER_EMAIL_ENCHANTEDLINK_PATH, Mixins::Common::EndpointsV1::UPDATE_USER_EMAIL_MAGICLINK_PATH, Mixins::Common::EndpointsV1::UPDATE_USER_EMAIL_OTP_PATH, Mixins::Common::EndpointsV1::UPDATE_USER_PHONE_MAGICLINK_PATH, Mixins::Common::EndpointsV1::UPDATE_USER_PHONE_OTP_PATH, Mixins::Common::EndpointsV1::VALIDATE_SESSION_PATH, Mixins::Common::EndpointsV1::VERIFY_CODE_AUTH_PATH, Mixins::Common::EndpointsV1::VERIFY_ENCHANTEDLINK_AUTH_PATH, Mixins::Common::EndpointsV1::VERIFY_MAGICLINK_AUTH_PATH, Mixins::Common::EndpointsV1::VERIFY_TOTP_PATH
Constants included from Mixins::Common
Mixins::Common::COOKIE_DATA_NAME, Mixins::Common::DEFAULT_BASE_URL, Mixins::Common::DEFAULT_JWT_VALIDATION_LEEWAY, Mixins::Common::DEFAULT_TIMEOUT_SECONDS, Mixins::Common::PHONE_REGEX, Mixins::Common::REDIRECT_LOCATION_COOKIE_NAME, Mixins::Common::REFRESH_SESSION_COOKIE_NAME, Mixins::Common::REFRESH_SESSION_TOKEN_NAME, Mixins::Common::SESSION_COOKIE_NAME, Mixins::Common::SESSION_TOKEN_NAME
Instance Method Summary collapse
- #exchange_access_key(access_key: nil, login_options: {}, audience: nil) ⇒ Object
- #generate_jwt_response(response_body: nil, refresh_cookie: nil, audience: nil) ⇒ Object
- #select_tenant(tenant_id: nil, refresh_token: nil) ⇒ Object
- #validate_permissions(jwt_response: nil, permissions: nil) ⇒ Object
- #validate_roles(jwt_response: nil, roles: nil) ⇒ Object
-
#validate_tenant_permissions(jwt_response: nil, tenant: nil, permissions: nil) ⇒ Object
rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/MethodLength.
- #validate_tenant_roles(jwt_response: nil, tenant: nil, roles: nil) ⇒ Object
- #validate_token(token, _audience = nil) ⇒ Object
Methods included from TOTP
#totp_add_update_key, #totp_sign_in_code, #totp_sign_up
Methods included from Mixins::Validation
#validate_code, #validate_email, #validate_login_id, #validate_password, #validate_phone, #validate_redirect_url, #validate_refresh_token_not_nil, #validate_scim_group_id, #validate_tenant, #validate_tenants, #validate_token_not_empty, #validate_user_id, #verify_provider
Methods included from Mixins::Common
#deep_copy, #get_method_string
Methods included from SAML
#saml_exchange_token, #saml_sign_in
Methods included from OTP
#otp_sign_in, #otp_sign_up, #otp_sign_up_or_in, #otp_update_user_email, #otp_update_user_phone, #otp_verify_code
Methods included from OAuth
#oauth_create_redirect_url_for_sign_in_request, #oauth_create_redirect_url_for_sign_up_request, #oauth_exchange_token, #oauth_start
Methods included from MagicLink
#magiclink_sign_in, #magiclink_sign_up, #magiclink_sign_up_or_in, #magiclink_update_user_email, #magiclink_update_user_phone, #magiclink_verify_token
Methods included from EnchantedLink
#enchanted_link_get_session, #enchanted_link_sign_in, #enchanted_link_sign_up, #enchanted_link_sign_up_or_in, #enchanted_link_update_user_email, #enchanted_link_verify_token
Methods included from Password
#get_password_policy, #password_replace, #password_reset, #password_sign_in, #password_sign_up, #password_update
Instance Method Details
#exchange_access_key(access_key: nil, login_options: {}, audience: nil) ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/descope/api/v1/auth.rb', line 43 def exchange_access_key(access_key: nil, login_options: {}, audience: nil) # Return a new session token for the given access key # Args: # access_key (str): The access key # audience (str|Iterable[str]|nil): Optional recipients that the JWT is intended for # (must be equal to the 'aud' claim on the provided token) # login_options (hash): Optional advanced controls over login parameters # Return value (Hash): returns the session token from the server together with the expiry and key id # (sessionToken:Hash, keyId:str, expiration:int) unless (access_key.is_a?(String) || access_key.nil?) && !access_key.to_s.empty? raise AuthException.new('Access key should be a string!', code: 400) end res = post(EXCHANGE_AUTH_ACCESS_KEY_PATH, { loginOptions: , audience: audience }, {}, access_key) generate_auth_info(res, nil, false, audience) end |
#generate_jwt_response(response_body: nil, refresh_cookie: nil, audience: nil) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/descope/api/v1/auth.rb', line 31 def generate_jwt_response(response_body: nil, refresh_cookie: nil, audience: nil) if response_body.nil? || response_body.empty? raise AuthException.new('Unable to generate jwt response. Response body is empty', code: 500) end jwt_response = generate_auth_info(response_body, , true, audience) jwt_response['user'] = response_body.key?('user') ? response_body['user'] : {} jwt_response['firstSeen'] = response_body.key?('firstSeen') ? response_body['firstSeen'] : true jwt_response end |
#select_tenant(tenant_id: nil, refresh_token: nil) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/descope/api/v1/auth.rb', line 60 def select_tenant(tenant_id: nil, refresh_token: nil) validate_refresh_token_not_nil(refresh_token) res = post(SELECT_TENANT_PATH, { tenantId: tenant_id }, {}, refresh_token) @logger.debug "select_tenant response: #{res}" = res.fetch('cookies') generate_jwt_response(response_body: res, refresh_cookie: .fetch(REFRESH_SESSION_COOKIE_NAME, nil)) generate_jwt_response( response_body: res, refresh_cookie: res['refreshJwt'] ) end |
#validate_permissions(jwt_response: nil, permissions: nil) ⇒ Object
72 73 74 75 76 |
# File 'lib/descope/api/v1/auth.rb', line 72 def (jwt_response: nil, permissions: nil) # Validate that a jwt_response has been granted the specified permissions. # For a multi-tenant environment use validate_tenant_permissions function (jwt_response: jwt_response, permissions: ) end |
#validate_roles(jwt_response: nil, roles: nil) ⇒ Object
123 124 125 126 127 |
# File 'lib/descope/api/v1/auth.rb', line 123 def validate_roles(jwt_response: nil, roles: nil) # Validate that a jwt_response has been granted the specified roles. # For a multi-tenant environment use validate_tenant_roles function validate_tenant_roles(jwt_response: jwt_response, tenant: '', roles: roles) end |
#validate_tenant_permissions(jwt_response: nil, tenant: nil, permissions: nil) ⇒ Object
rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/MethodLength
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/descope/api/v1/auth.rb', line 79 def (jwt_response: nil, tenant: nil, permissions: nil) # Validate that a jwt_response has been granted the specified permissions on the specified tenant. # For a multi-tenant environment use validate_tenant_permissions function if .is_a?(String) = [] else ||= [] end unless jwt_response.is_a?(Hash) raise Descope::ArgumentException.new( 'Invalid JWT response hash', code: 400 ) end return false unless jwt_response = if tenant.nil? || tenant.to_s.empty? jwt_response.fetch('permissions', []) else # ensure that the tenant is associated with the jwt_response @logger.debug "tenant associated jwt: #{jwt_response['tenants']&.key?(tenant)}" return false unless jwt_response['tenants'].key?(tenant) # dig is a method in Ruby for safely navigating nested data structures like hashes # and arrays. It allows you to access deeply nested values without worrying about # raising an error if a middle value is nil. = jwt_response.dig('tenants', tenant, 'permissions') || [] = [] if .nil? if .is_a?(String) @logger.debug "tenant_permission string: #{tenant_permission}" [] else @logger.debug "tenant_permission array: #{tenant_permission}" end end # Validate all permissions are granted .all? do || .include?() end end |
#validate_tenant_roles(jwt_response: nil, tenant: nil, roles: nil) ⇒ Object
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/descope/api/v1/auth.rb', line 129 def validate_tenant_roles(jwt_response: nil, tenant: nil, roles: nil) # Validate that a jwt_response has been granted the specified roles on the specified tenant. # For a multi-tenant environment use validate_tenant_roles function @logger.debug "Validate_tenant_roles: #{jwt_response}, #{tenant}, #{roles}" if roles.is_a?(String) roles = [roles] else roles ||= [] end unless jwt_response.is_a?(Hash) raise Descope::ArgumentException.new( 'Invalid JWT response hash', code: 400 ) end return false unless jwt_response granted_roles = if tenant.nil? || tenant.to_s.empty? jwt_response.fetch('roles', []) else # ensure that the tenant is associated with the jwt_response return false unless jwt_response['tenants'].key?(tenant) # dig is a method in Ruby for safely navigating nested data structures like hashes # and arrays. It allows you to access deeply nested values without worrying about # raising an error if a middle value is nil. tenant_roles = jwt_response.dig('tenants', tenant, 'roles') || [] tenant_roles = [] if tenant_roles.nil? if tenant_roles.is_a?(String) [tenant_roles] else tenant_roles end end @logger.debug "granted_roles: #{granted_roles}" # Validate all roles are granted roles.all? do |role| @logger.debug "granted_roles.include?(#{role}): #{granted_roles.include?(role)}" granted_roles.include?(role) end end |
#validate_token(token, _audience = nil) ⇒ Object
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/descope/api/v1/auth.rb', line 173 def validate_token(token, _audience = nil) @logger.debug "validating token: #{token}" raise AuthException.new('Token validation received empty token', code: 500) if token.nil? || token.to_s.empty? unverified_header = jwt_get_unverified_header(token) @logger.debug "unverified_header: #{unverified_header}" alg_header = unverified_header[ALGORITHM_KEY] @logger.debug "alg_header: #{alg_header}" if alg_header.nil? || alg_header == 'none' raise AuthException.new('Token header is missing property: alg', code: 500) end kid = unverified_header['kid'] @logger.debug "kid: #{kid}" raise AuthException.new('Token header is missing property: kid', code: 500) if kid.nil? found_key = nil @mlock.synchronize do if @public_keys.nil? || @public_keys == {} || @public_keys.to_s.empty? || @public_keys[kid].nil? @logger.debug 'fetching public keys' # fetch keys from /v2/keys and set them in @public_keys fetch_public_keys end found_key = @public_keys[kid] @logger.debug "found_key: #{found_key}" raise AuthException.new('Unable to validate public key. Public key not found.', code: 500) if found_key.nil? end # save reference to the found key # (as another thread can change the self.public_keys hash) @logger.debug 'checking if alg_header matches alg_from_key' alg_from_key = found_key[1] if alg_header != alg_from_key raise AuthException.new( 'Algorithm signature in JWT header does not match the algorithm signature in the Public key.', code: 500 ) end begin @logger.debug 'decoding token' claims = JWT.decode( token, found_key[0].public_key, true, { algorithm: alg_header, exp_leeway: @jwt_validation_leeway } )[0] # the payload is the first index in the decoded array rescue JWT::ExpiredSignature => e raise AuthException.new( "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 ) end claims['jwt'] = token @logger.debug "claims: #{claims}" claims end |