diff --git a/src/SAS9ApiClient.ts b/src/SAS9ApiClient.ts index c7e171c..6a66caf 100644 --- a/src/SAS9ApiClient.ts +++ b/src/SAS9ApiClient.ts @@ -1,6 +1,7 @@ import { generateTimestamp } from '@sasjs/utils/time' import * as NodeFormData from 'form-data' import { Sas9RequestClient } from './request/Sas9RequestClient' +import { HttpsAgent } from './types/HttpsAgent' import { isUrl } from './utils' /** @@ -13,10 +14,13 @@ export class SAS9ApiClient { constructor( private serverUrl: string, private jobsPath: string, - allowInsecureRequests: boolean + httpsAgentConfiguration?: HttpsAgent ) { if (serverUrl) isUrl(serverUrl) - this.requestClient = new Sas9RequestClient(serverUrl, allowInsecureRequests) + this.requestClient = new Sas9RequestClient( + serverUrl, + httpsAgentConfiguration + ) } /** diff --git a/src/SASjs.ts b/src/SASjs.ts index 74831a5..d5f7853 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -36,7 +36,7 @@ const defaultConfig: SASjsConfig = { debug: false, contextName: 'SAS Job Execution compute context', useComputeApi: null, - allowInsecureRequests: false, + httpsAgentConfiguration: {}, loginMechanism: LoginMechanism.Default } @@ -57,7 +57,7 @@ export default class SASjs { private jesJobExecutor: JobExecutor | null = null private sas9JobExecutor: JobExecutor | null = null - constructor(config?: any) { + constructor(config?: Partial) { this.sasjsConfig = { ...defaultConfig, ...config @@ -792,7 +792,7 @@ export default class SASjs { sasApiClient = new SAS9ApiClient( serverUrl, this.jobsPath, - this.sasjsConfig.allowInsecureRequests + this.sasjsConfig.httpsAgentConfiguration ) } } else { @@ -951,12 +951,12 @@ export default class SASjs { if (!this.requestClient) { this.requestClient = new RequestClient( this.sasjsConfig.serverUrl, - this.sasjsConfig.allowInsecureRequests + this.sasjsConfig.httpsAgentConfiguration ) } else { this.requestClient.setConfig( this.sasjsConfig.serverUrl, - this.sasjsConfig.allowInsecureRequests + this.sasjsConfig.httpsAgentConfiguration ) } @@ -995,7 +995,7 @@ export default class SASjs { this.sas9ApiClient = new SAS9ApiClient( this.sasjsConfig.serverUrl, this.jobsPath, - this.sasjsConfig.allowInsecureRequests + this.sasjsConfig.httpsAgentConfiguration ) } @@ -1018,7 +1018,7 @@ export default class SASjs { this.sasjsConfig.serverUrl, this.sasjsConfig.serverType!, this.jobsPath, - this.sasjsConfig.allowInsecureRequests + this.sasjsConfig.httpsAgentConfiguration ) this.computeJobExecutor = new ComputeJobExecutor( diff --git a/src/job-execution/Sas9JobExecutor.ts b/src/job-execution/Sas9JobExecutor.ts index 892be05..b4fb549 100644 --- a/src/job-execution/Sas9JobExecutor.ts +++ b/src/job-execution/Sas9JobExecutor.ts @@ -4,6 +4,7 @@ import { ErrorResponse } from '../types/errors' import { convertToCSV, isRelativePath } from '../utils' import { BaseJobExecutor } from './JobExecutor' import { Sas9RequestClient } from '../request/Sas9RequestClient' +import { HttpsAgent } from '../types/HttpsAgent' /** * Job executor for SAS9 servers for use in Node.js environments. @@ -17,10 +18,13 @@ export class Sas9JobExecutor extends BaseJobExecutor { serverUrl: string, serverType: ServerType, private jobsPath: string, - allowInsecureRequests: boolean + httpsAgentConfiguration: HttpsAgent ) { super(serverUrl, serverType) - this.requestClient = new Sas9RequestClient(serverUrl, allowInsecureRequests) + this.requestClient = new Sas9RequestClient( + serverUrl, + httpsAgentConfiguration + ) } async execute(sasJob: string, data: any, config: any) { diff --git a/src/request/RequestClient.ts b/src/request/RequestClient.ts index c84face..f4a0f29 100644 --- a/src/request/RequestClient.ts +++ b/src/request/RequestClient.ts @@ -13,6 +13,7 @@ import { parseWeboutResponse } from '../utils/parseWeboutResponse' import { prefixMessage } from '@sasjs/utils/error' import { SAS9AuthError } from '../types/errors/SAS9AuthError' import { parseGeneratedCode, parseSourceCode } from '../utils' +import { HttpsAgent } from '../types/HttpsAgent' export interface HttpClient { get( @@ -54,12 +55,12 @@ export class RequestClient implements HttpClient { protected fileUploadCsrfToken: CsrfToken | undefined protected httpClient!: AxiosInstance - constructor(protected baseUrl: string, allowInsecure = false) { - this.createHttpClient(baseUrl, allowInsecure) + constructor(protected baseUrl: string, httpsAgentConfiguration?: HttpsAgent) { + this.createHttpClient(baseUrl, httpsAgentConfiguration) } - public setConfig(baseUrl: string, allowInsecure = false) { - this.createHttpClient(baseUrl, allowInsecure) + public setConfig(baseUrl: string, httpsAgentConfiguration?: HttpsAgent) { + this.createHttpClient(baseUrl, httpsAgentConfiguration) } public getCsrfToken(type: 'general' | 'file' = 'general') { @@ -511,15 +512,24 @@ export class RequestClient implements HttpClient { return responseToReturn } - private createHttpClient(baseUrl: string, allowInsecure = false) { + private createHttpClient( + baseUrl: string, + httpsAgentConfiguration: HttpsAgent = {} + ) { const https = require('https') - if (allowInsecure && https.Agent) { - this.httpClient = axios.create({ - baseURL: baseUrl, - httpsAgent: new https.Agent({ - rejectUnauthorized: !allowInsecure - }) - }) + const { selfSigned, clientCA, allowInsecure } = httpsAgentConfiguration + + const httpsAgentConfig = selfSigned + ? { ca: selfSigned.ca } + : clientCA + ? { key: clientCA.key, cert: clientCA.cert, requestCert: true } + : allowInsecure + ? { rejectUnauthorized: !allowInsecure } + : undefined + + if (httpsAgentConfig) { + const httpsAgent = new https.Agent(httpsAgentConfig) + this.httpClient = axios.create({ httpsAgent }) } else { this.httpClient = axios.create({ baseURL: baseUrl diff --git a/src/request/Sas9RequestClient.ts b/src/request/Sas9RequestClient.ts index 5fb4750..9f7fe2b 100644 --- a/src/request/Sas9RequestClient.ts +++ b/src/request/Sas9RequestClient.ts @@ -3,14 +3,15 @@ import axiosCookieJarSupport from 'axios-cookiejar-support' import * as tough from 'tough-cookie' import { prefixMessage } from '@sasjs/utils/error' import { RequestClient, throwIfError } from './RequestClient' +import { HttpsAgent } from '../types/HttpsAgent' /** * Specific request client for SAS9 in Node.js environments. * Handles redirects and cookie management. */ export class Sas9RequestClient extends RequestClient { - constructor(baseUrl: string, allowInsecure = false) { - super(baseUrl, allowInsecure) + constructor(baseUrl: string, httpsAgentConfiguration?: HttpsAgent) { + super(baseUrl, httpsAgentConfiguration) this.httpClient.defaults.maxRedirects = 0 this.httpClient.defaults.validateStatus = (status) => status >= 200 && status < 303 diff --git a/src/types/HttpsAgent.ts b/src/types/HttpsAgent.ts new file mode 100644 index 0000000..5932599 --- /dev/null +++ b/src/types/HttpsAgent.ts @@ -0,0 +1,10 @@ +export interface HttpsAgent { + selfSigned?: { + ca: string[] + } + clientCA?: { + key: string + cert: string + } + allowInsecure?: boolean +} diff --git a/src/types/SASjsConfig.ts b/src/types/SASjsConfig.ts index b562dcb..5fb6dad 100644 --- a/src/types/SASjsConfig.ts +++ b/src/types/SASjsConfig.ts @@ -1,4 +1,5 @@ import { ServerType } from '@sasjs/utils/types' +import { HttpsAgent } from './HttpsAgent' /** * Specifies the configuration for the SASjs instance - eg where and how to @@ -58,7 +59,7 @@ export class SASjsConfig { * When set to `true`, the adapter will allow requests to SAS servers that use a self-signed SSL certificate. * Changing this setting is not recommended. */ - allowInsecureRequests = false + httpsAgentConfiguration: HttpsAgent = {} /** * Supported login mechanisms are - Redirected and Default */ diff --git a/src/types/index.ts b/src/types/index.ts index 2303619..3998768 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -2,6 +2,7 @@ export * from './Context' export * from './CsrfToken' export * from './Folder' export * from './File' +export * from './HttpsAgent' export * from './Job' export * from './JobDefinition' export * from './JobResult'