142 lines
4.5 KiB
TypeScript
142 lines
4.5 KiB
TypeScript
import { handleErrorsDecorator as handleErrors } from '@botpress/common'
|
|
import { RuntimeError } from '@botpress/sdk'
|
|
import axios, { type AxiosInstance } from 'axios'
|
|
import type { CommonHandlerProps, Result } from '../types'
|
|
import {
|
|
type GetAccessTokenResp,
|
|
docusignOAuthAccessTokenRespSchema,
|
|
type GetUserInfoResp,
|
|
getUserInfoRespSchema,
|
|
} from './schemas'
|
|
import { GetAccessTokenParams } from './types'
|
|
import * as bp from '.botpress'
|
|
|
|
type OAuthParameters = {
|
|
oauthBaseUrl: string
|
|
clientId: string
|
|
clientSecret: string
|
|
}
|
|
|
|
export class DocusignAuthClient {
|
|
private _axiosClient: AxiosInstance
|
|
|
|
private constructor(params: OAuthParameters) {
|
|
const { oauthBaseUrl, clientId, clientSecret } = params
|
|
|
|
// Opted for axios here since the docusign package only has
|
|
// a function for getting an accessToken from the oauth code
|
|
// but not for refresh tokens
|
|
this._axiosClient = axios.create({
|
|
baseURL: oauthBaseUrl,
|
|
headers: {
|
|
Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`,
|
|
'Cache-Control': 'no-store',
|
|
Pragma: 'no-cache',
|
|
},
|
|
})
|
|
}
|
|
|
|
private async _getAccessToken(params: GetAccessTokenParams): Promise<Result<GetAccessTokenResp>> {
|
|
// The Docusign API Postman collection example uses FormData for this endpoint.
|
|
const formData = new FormData()
|
|
Object.entries(params).forEach(([key, value]) => formData.append(key, value))
|
|
|
|
// Docusign doesn't return a timestamp when a token is issued. So a timestamp
|
|
// is generated prior to the request being made so the expiry time is accurate.
|
|
const tokenRequestedAt = Date.now()
|
|
|
|
const resp = await this._axiosClient.post('/oauth/token', formData)
|
|
|
|
if (resp.status < 200 || resp.status >= 300) {
|
|
return {
|
|
success: false,
|
|
error: new RuntimeError(
|
|
`Failed to retrieve access token w/${params.grant_type} | Invalid Status '${resp.status}'`
|
|
),
|
|
}
|
|
}
|
|
|
|
const result = docusignOAuthAccessTokenRespSchema.safeParse(resp.data)
|
|
if (!result.success) {
|
|
return {
|
|
success: false,
|
|
error: new RuntimeError(`Failed to retrieve access token w/${params.grant_type} | Schema Parse Failure`),
|
|
}
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
accessToken: result.data.access_token,
|
|
tokenType: result.data.token_type,
|
|
expiresAt: tokenRequestedAt + result.data.expires_in * 1000,
|
|
refreshToken: result.data.refresh_token,
|
|
},
|
|
}
|
|
}
|
|
|
|
@handleErrors('Failed to obtain Docusign OAuth access token from authorization code')
|
|
public async getAccessTokenWithCode(code: string): Promise<Result<GetAccessTokenResp>> {
|
|
return this._getAccessToken({
|
|
grant_type: 'authorization_code',
|
|
code,
|
|
})
|
|
}
|
|
|
|
@handleErrors('Failed to refresh Docusign OAuth access token')
|
|
public async getAccessTokenWithRefreshToken(refreshToken: string): Promise<Result<GetAccessTokenResp>> {
|
|
return this._getAccessToken({
|
|
grant_type: 'refresh_token',
|
|
refresh_token: refreshToken,
|
|
})
|
|
}
|
|
|
|
@handleErrors('Failed to retrieve Docusign user info')
|
|
public async getUserInfo(accessToken: string, tokenType: string): Promise<Result<GetUserInfoResp>> {
|
|
const resp = await this._axiosClient.get('/oauth/userinfo', {
|
|
headers: {
|
|
Authorization: `${tokenType} ${accessToken}`,
|
|
},
|
|
})
|
|
|
|
if (resp.status < 200 || resp.status >= 300) {
|
|
return {
|
|
success: false,
|
|
error: new RuntimeError(`Failed to retrieve user info | Invalid Status '${resp.status}'`),
|
|
}
|
|
}
|
|
|
|
const result = getUserInfoRespSchema.safeParse(resp.data)
|
|
if (!result.success) {
|
|
return {
|
|
success: false,
|
|
error: new RuntimeError('Failed to retrieve user info | Schema Parse Failure'),
|
|
}
|
|
}
|
|
|
|
return { success: true, data: result.data }
|
|
}
|
|
|
|
public static create(props: CommonHandlerProps): DocusignAuthClient {
|
|
const { ctx } = props
|
|
switch (ctx.configurationType) {
|
|
case 'sandbox':
|
|
return new DocusignAuthClient({
|
|
oauthBaseUrl: bp.secrets.SANDBOX_OAUTH_BASE_URL,
|
|
clientId: bp.secrets.SANDBOX_CLIENT_ID,
|
|
clientSecret: bp.secrets.SANDBOX_CLIENT_SECRET,
|
|
})
|
|
case null:
|
|
return new DocusignAuthClient({
|
|
oauthBaseUrl: bp.secrets.OAUTH_BASE_URL,
|
|
clientId: bp.secrets.CLIENT_ID,
|
|
clientSecret: bp.secrets.CLIENT_SECRET,
|
|
})
|
|
default:
|
|
ctx satisfies never
|
|
}
|
|
|
|
throw new RuntimeError(`Unsupported configuration type: ${props.ctx.configurationType}`)
|
|
}
|
|
}
|