1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-02 02:00:06 +00:00

feat(get-token): improved error prefix

This commit is contained in:
Yury Shkoda
2023-06-21 16:33:45 +03:00
parent 7d826685f7
commit 375cec48ca
6 changed files with 152 additions and 10 deletions

View File

@@ -1,5 +1,7 @@
import { prefixMessage } from '@sasjs/utils/error' import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from '../request/RequestClient' import { RequestClient } from '../request/RequestClient'
import { getTokenRequestErrorPrefix } from './getTokenRequestErrorPrefix'
import { ServerType } from '@sasjs/utils'
/** /**
* Exchanges the auth code for an access token for the given client. * Exchanges the auth code for an access token for the given client.
@@ -31,6 +33,16 @@ export async function getAccessTokenForSasjs(
} }
}) })
.catch((err) => { .catch((err) => {
throw prefixMessage(err, 'Error while getting access token. ') throw prefixMessage(
err,
getTokenRequestErrorPrefix(
'fetching access token',
'getAccessTokenForSasjs',
ServerType.Sasjs,
url,
data,
clientId
)
)
}) })
} }

View File

@@ -1,11 +1,12 @@
import { SasAuthResponse } from '@sasjs/utils/types' import { SasAuthResponse, ServerType } from '@sasjs/utils/types'
import { prefixMessage } from '@sasjs/utils/error' import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from '../request/RequestClient' import { RequestClient } from '../request/RequestClient'
import { CertificateError } from '../types/errors' import { CertificateError } from '../types/errors'
import { getTokenRequestErrorPrefix } from './getTokenRequestErrorPrefix'
/** /**
* Exchanges the auth code for an access token for the given client. * Exchange the auth code for access / refresh tokens for the given client / secret pair.
* @param requestClient - the pre-configured HTTP request client * @param requestClient - the pre-configured HTTP request client.
* @param clientId - the client ID to authenticate with. * @param clientId - the client ID to authenticate with.
* @param clientSecret - the client secret to authenticate with. * @param clientSecret - the client secret to authenticate with.
* @param authCode - the auth code received from the server. * @param authCode - the auth code received from the server.
@@ -16,29 +17,44 @@ export async function getAccessTokenForViya(
clientSecret: string, clientSecret: string,
authCode: string authCode: string
): Promise<SasAuthResponse> { ): Promise<SasAuthResponse> {
const url = '/SASLogon/oauth/token'
let token let token
if (typeof Buffer === 'undefined') { if (typeof Buffer === 'undefined') {
token = btoa(clientId + ':' + clientSecret) token = btoa(clientId + ':' + clientSecret)
} else { } else {
token = Buffer.from(clientId + ':' + clientSecret).toString('base64') token = Buffer.from(clientId + ':' + clientSecret).toString('base64')
} }
const url = '/SASLogon/oauth/token'
const headers = { const headers = {
Authorization: 'Basic ' + token, Authorization: 'Basic ' + token,
Accept: 'application/json' Accept: 'application/json'
} }
const data = new URLSearchParams({ const dataJson = new URLSearchParams({
grant_type: 'authorization_code', grant_type: 'authorization_code',
code: authCode code: authCode
}) })
const data = new URLSearchParams(dataJson)
const authResponse = await requestClient const authResponse = await requestClient
.post(url, data, undefined, 'application/x-www-form-urlencoded', headers) .post(url, data, undefined, 'application/x-www-form-urlencoded', headers)
.then((res) => res.result as SasAuthResponse) .then((res) => res.result as SasAuthResponse)
.catch((err) => { .catch((err) => {
if (err instanceof CertificateError) throw err if (err instanceof CertificateError) throw err
throw prefixMessage(err, 'Error while getting access token. ') throw prefixMessage(
err,
getTokenRequestErrorPrefix(
'fetching access token',
'getAccessTokenForViya',
ServerType.SasViya,
url,
dataJson,
headers,
clientId,
clientSecret
)
)
}) })
return authResponse return authResponse

View File

@@ -0,0 +1,88 @@
import { ServerType } from '@sasjs/utils/types'
type Server = ServerType.SasViya | ServerType.Sasjs
type Operation = 'fetching access token' | 'refreshing tokens'
const getServerName = (server: Server) =>
server === ServerType.SasViya ? 'Viya' : 'Sasjs'
const getResponseTitle = (server: Server) =>
`Response from ${getServerName(server)} is below.`
/**
* Forms error prefix for requests related to token operations.
* @param operation - string describing operation ('fetching access token' or 'refreshing tokens').
* @param funcName - name of the function sent the request.
* @param server - server type (SASVIYA or SASJS).
* @param url - endpoint used to send the request.
* @param data - request payload.
* @param headers - request headers.
* @param clientId - client ID to authenticate with.
* @param clientSecret - client secret to authenticate with.
* @returns - string containing request information. Example:
* Error while fetching access token from /SASLogon/oauth/token
* Thrown by the @sasjs/adapter getAccessTokenForViya function.
* Payload:
* {
* "grant_type": "authorization_code",
* "code": "example_code"
* }
* Headers:
* {
* "Authorization": "Basic NEdMQXBwOjRHTEFwcDE=",
* "Accept": "application/json"
* }
* ClientId: exampleClientId
* ClientSecret: exampleClientSecret
*
* Response from Viya is below.
* Auth error: {
* "error": "invalid_token",
* "error_description": "No scopes were granted"
* }
*/
export const getTokenRequestErrorPrefix = (
operation: Operation,
funcName: string,
server: Server,
url: string,
data?: {},
headers?: {},
clientId?: string,
clientSecret?: string
) => {
const stringify = (obj: {}) => JSON.stringify(obj, null, 2)
const lines = [
`Error while ${operation} from ${url}`,
`Thrown by the @sasjs/adapter ${funcName} function.`
]
if (data) {
lines.push('Payload:')
lines.push(stringify(data))
}
if (headers) {
lines.push('Headers:')
lines.push(stringify(headers))
}
if (clientId) lines.push(`ClientId: ${clientId}`)
if (clientSecret) lines.push(`ClientSecret: ${clientSecret}`)
lines.push('')
lines.push(`${getResponseTitle(server)}`)
lines.push('')
return lines.join(`\n`)
}
/**
* Parse error prefix to get response payload.
* @param prefix - error prefix generated by getTokenRequestErrorPrefix function.
* @param server - server type (SASVIYA or SASJS).
* @returns - response payload.
*/
export const getTokenRequestErrorPrefixResponse = (
prefix: string,
server: ServerType.SasViya | ServerType.Sasjs
) => prefix.split(`${getResponseTitle(server)}\n`).pop() as string

View File

@@ -22,6 +22,7 @@ export async function getTokens(
): Promise<AuthConfig> { ): Promise<AuthConfig> {
const logger = process.logger || console const logger = process.logger || console
let { access_token, refresh_token, client, secret } = authConfig let { access_token, refresh_token, client, secret } = authConfig
if ( if (
isAccessTokenExpiring(access_token) || isAccessTokenExpiring(access_token) ||
isRefreshTokenExpiring(refresh_token) isRefreshTokenExpiring(refresh_token)
@@ -29,6 +30,7 @@ export async function getTokens(
if (hasTokenExpired(refresh_token)) { if (hasTokenExpired(refresh_token)) {
const error = const error =
'Unable to obtain new access token. Your refresh token has expired.' 'Unable to obtain new access token. Your refresh token has expired.'
logger.error(error) logger.error(error)
throw new Error(error) throw new Error(error)
@@ -47,5 +49,6 @@ export async function getTokens(
: await refreshTokensForSasjs(requestClient, refresh_token) : await refreshTokensForSasjs(requestClient, refresh_token)
;({ access_token, refresh_token } = tokens) ;({ access_token, refresh_token } = tokens)
} }
return { access_token, refresh_token, client, secret } return { access_token, refresh_token, client, secret }
} }

View File

@@ -1,5 +1,7 @@
import { prefixMessage } from '@sasjs/utils/error' import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from '../request/RequestClient' import { RequestClient } from '../request/RequestClient'
import { getTokenRequestErrorPrefix } from './getTokenRequestErrorPrefix'
import { ServerType } from '@sasjs/utils'
/** /**
* Exchanges the refresh token for an access token for the given client. * Exchanges the refresh token for an access token for the given client.
@@ -28,7 +30,15 @@ export async function refreshTokensForSasjs(
} }
}) })
.catch((err) => { .catch((err) => {
throw prefixMessage(err, 'Error while refreshing tokens: ') throw prefixMessage(
err,
getTokenRequestErrorPrefix(
'refreshing tokens',
'refreshTokensForSasjs',
ServerType.Sasjs,
url
)
)
}) })
return authResponse return authResponse

View File

@@ -1,8 +1,9 @@
import { SasAuthResponse } from '@sasjs/utils/types' import { SasAuthResponse, ServerType } from '@sasjs/utils/types'
import { prefixMessage } from '@sasjs/utils/error' import { prefixMessage } from '@sasjs/utils/error'
import * as NodeFormData from 'form-data' import * as NodeFormData from 'form-data'
import { RequestClient } from '../request/RequestClient' import { RequestClient } from '../request/RequestClient'
import { isNode } from '../utils' import { isNode } from '../utils'
import { getTokenRequestErrorPrefix } from './getTokenRequestErrorPrefix'
/** /**
* Exchanges the refresh token for an access token for the given client. * Exchanges the refresh token for an access token for the given client.
@@ -46,7 +47,19 @@ export async function refreshTokensForViya(
) )
.then((res) => res.result) .then((res) => res.result)
.catch((err) => { .catch((err) => {
throw prefixMessage(err, 'Error while refreshing tokens: ') throw prefixMessage(
err,
getTokenRequestErrorPrefix(
'refreshing tokens',
'refreshTokensForViya',
ServerType.SasViya,
url,
formData,
headers,
clientId,
clientSecret
)
)
}) })
return authResponse return authResponse