auditjs
Version:
Audit dependencies to identify known vulnerabilities and maintenance problems
159 lines • 7.25 kB
JavaScript
;
/*
* Copyright 2019-Present Sonatype Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GuideRequestService = void 0;
const sonatype_guide_api_client_1 = require("@sonatype/sonatype-guide-api-client");
const node_persist_1 = __importDefault(require("node-persist"));
const path_1 = __importDefault(require("path"));
const os_1 = require("os");
const RequestHelpers_1 = require("./RequestHelpers");
const GUIDE_BASE_URL = 'https://api.guide.sonatype.com';
const MAX_COORDINATES = 128;
const CACHE_PATH = path_1.default.join((0, os_1.homedir)(), '.sonatype-guide', 'auditjs');
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
class GuideRequestService {
constructor(username, token, cacheLocation = CACHE_PATH, server = GUIDE_BASE_URL, accessToken) {
this.username = username;
this.token = token;
this.cacheLocation = cacheLocation;
this.server = server;
this.accessToken = accessToken;
// Create a custom fetch wrapper that includes proxy support via dispatcher
const proxyAgent = RequestHelpers_1.RequestHelpers.getHttpAgent();
const fetchApi = proxyAgent
? (url, init) => fetch(url, { ...init, dispatcher: proxyAgent })
: undefined;
// OSSIndexCompatibilityApi only supports HTTP Basic auth.
// In PAT-only mode (no username), send the PAT as password with empty username
// so the generated client includes an Authorization: Basic :<PAT> header.
const ossUsername = username ?? (accessToken ? '' : undefined);
const ossPassword = token ?? accessToken;
this.api = new sonatype_guide_api_client_1.OSSIndexCompatibilityApi(new sonatype_guide_api_client_1.Configuration({ username: ossUsername, password: ossPassword, basePath: server, fetchApi }));
// RecommendationsApi supports Bearer auth; fall back to Basic when username is present.
const recConfig = accessToken && !username
? new sonatype_guide_api_client_1.Configuration({ accessToken, basePath: server, fetchApi })
: new sonatype_guide_api_client_1.Configuration({ username, password: token, basePath: server, fetchApi });
this.recommendationsApi = new sonatype_guide_api_client_1.RecommendationsApi(recConfig);
}
async getRecommendations(purls) {
const results = new Map();
await Promise.allSettled(purls.map(async (purl) => {
try {
const response = await this.recommendationsApi.getRecommendations({ recommendationRequest: { purl } });
results.set(purl, response);
}
catch {
// skip components where recommendations are unavailable
}
}));
return results;
}
chunkData(data) {
const chunks = [];
const copy = [...data];
while (copy.length > 0) {
chunks.push(copy.splice(0, MAX_COORDINATES));
}
return chunks;
}
combineResponseChunks(data) {
return [].concat(...data);
}
combineCacheAndResponses(combinedChunks, dataInCache) {
return combinedChunks.concat(dataInCache);
}
async insertResponsesIntoCache(response) {
for (let i = 0; i < response.length; i++) {
await node_persist_1.default.setItem(response[i].coordinates, response[i]);
}
return response;
}
async checkIfResultsAreInCache(data, format = 'npm') {
const inCache = new Array();
const notInCache = new Array();
for (let i = 0; i < data.length; i++) {
const coord = data[i];
const dataInCache = await node_persist_1.default.getItem(coord.toPurl(format));
if (dataInCache) {
inCache.push(dataInCache);
}
else {
notInCache.push(coord);
}
}
return new PurlContainer(inCache, notInCache);
}
async getResultsFromGuide(purls) {
try {
// PAT tokens require Bearer auth; the OSSIndexCompatibilityApi SDK only supports Basic by default.
const initOverrides = this.accessToken
? async ({ init }) => ({ ...init, headers: { ...init.headers, Authorization: `Bearer ${this.accessToken}` } })
: undefined;
const response = await this.api.getComponentReports({ purlRequestPost: { coordinates: purls } }, initOverrides);
return response;
}
catch (err) {
const status = (err instanceof Object && 'response' in err && err.response?.status) ||
undefined;
const detail = status ? ` (HTTP ${status})` : '';
throw new Error(`There was an error making the request to Sonatype Guide: ${err}${detail}`);
}
}
/**
* Posts to Sonatype Guide OSS Index Compatibility API, returns Promise of json object of response.
* Results are cached for 24 hours.
* @param data - {@link Coordinates} Array
* @param format - purl format string (e.g. 'npm')
* @returns a {@link Promise} of all Responses
*/
async callGuideOrGetFromCache(data, format = 'npm') {
await node_persist_1.default.init({ dir: this.cacheLocation, ttl: TWENTY_FOUR_HOURS });
const responses = [];
const results = await this.checkIfResultsAreInCache(data, format);
const chunkedPurls = this.chunkData(results.notInCache);
for (const chunk of chunkedPurls) {
try {
const purls = chunk.map((x) => x.toPurl(format)).filter((p) => /^[^@]+@\d+\.\d+\.\d+/.test(p));
if (purls.length === 0)
continue;
const res = this.getResultsFromGuide(purls);
responses.push(res);
}
catch (e) {
throw new Error(String(e));
}
}
return Promise.all(responses)
.then((resolvedResponses) => this.combineResponseChunks(resolvedResponses))
.then((combinedResponses) => this.insertResponsesIntoCache(combinedResponses))
.then((combinedResponses) => this.combineCacheAndResponses(combinedResponses, results.inCache))
.catch((err) => {
throw err;
});
}
}
exports.GuideRequestService = GuideRequestService;
class PurlContainer {
constructor(inCache, notInCache) {
this.inCache = inCache;
this.notInCache = notInCache;
}
}
//# sourceMappingURL=GuideRequestService.js.map