UNPKG

auditjs

Version:

Audit dependencies to identify known vulnerabilities and maintenance problems

159 lines 7.25 kB
"use strict"; /* * 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