UNPKG

@sap/cli-core

Version:

Command-Line Interface (CLI) Core Module

143 lines (142 loc) 5.08 kB
import { CACHE_SECRETS_FILE } from "../../constants.js"; import { get as getLogger } from "../../logger/index.js"; import { logVerbose } from "../../logger/utils.js"; import { ALLOWED_SECRET_TYPES } from "../../types.js"; import { deleteFile, readFile, writeFile } from "../cache.js"; import { getTenantUrl, isCustomClient, isSecretConsistent, updateUrls, } from "./utils.js"; export class SecretsStorageImpl { secrets = []; logger; constructor() { this.logger = getLogger("SecretsStorageImpl"); } async deleteAllSecrets() { await deleteFile(CACHE_SECRETS_FILE); this.secrets = []; } async recreateCalculatedValues() { for (let i = 0; i < this.secrets.length; i++) { this.secrets[i].id = i; if (this.secrets[i].client_id) { this.secrets[i].customClient = isCustomClient(this.secrets[i].client_id); // eslint-disable-next-line no-await-in-loop await this.updateUrls(i); } else { this.logger.warn(`client id is not set for secret with ID ${i}, skipping update of secret information`); } } } async initializeStorage() { try { this.secrets = JSON.parse(await readFile(CACHE_SECRETS_FILE)); if (!Array.isArray(this.secrets)) { this.logger.warn("secrets data is no array, initializing secrets to empty array"); this.secrets = []; } this.removeInconsistentSecrets(); await this.recreateCalculatedValues(); } catch (err) { logVerbose(this.logger, "The secrets file could not be read."); this.logger.warn("failed to read or parse secrets file", err); } } async getDefaultSecret() { return this.getSecretById(await this.getDefaultSecretId()); } async getDefaultSecretId() { const tenantUrl = getTenantUrl(); const { host } = new URL(tenantUrl); const secret = this.secrets.find((s) => new URL(s.tenantUrl).host === host); if (!secret) { throw new Error(`No secret found for host ${host}`); } return secret.id; } ensureSecretsExist(id) { return !!this.secrets.at(id); } throwIfSecretsDoesntExist(id) { if (!this.ensureSecretsExist(id)) { throw new Error(`no secrets exist for id ${id}`); } } async getSecretById(id) { this.throwIfSecretsDoesntExist(id); return this.secrets.at(id); } removeSecretsFromArray(id) { this.secrets.splice(id, 1); } updateSecret(secret) { const tenantUrl = getTenantUrl(); let prevSecretIx = this.secrets.findIndex((s) => s.tenantUrl === tenantUrl); let newSecret; if (prevSecretIx > -1) { // Overwrite the old secret completely except for id and tenantUrl newSecret = { ...secret, id: this.secrets[prevSecretIx].id, tenantUrl, customClient: false, }; this.secrets[prevSecretIx] = newSecret; } else { newSecret = { ...secret, id: this.secrets.length, tenantUrl, customClient: false, }; this.secrets.push(newSecret); prevSecretIx = this.secrets.length - 1; } return prevSecretIx; } async updateUrls(secretIndex) { this.secrets[secretIndex] = await updateUrls(this.secrets[secretIndex]); } async storeSecret(secret) { this.updateSecret(secret); await this.recreateCalculatedValues(); } deleteSecretById(id) { this.throwIfSecretsDoesntExist(id); this.removeSecretsFromArray(id); } removeInconsistentSecrets() { this.secrets = this.secrets.filter((secret) => { const consistent = isSecretConsistent(secret); if (!consistent.consistent) { this.logger.warn(`secret ${JSON.stringify(secret)} is not consistent and removed from cache. ${consistent.errors.join(". ")}`); return false; } return true; }); } removeUnknownPropertiesFromSecrets() { this.secrets.forEach((secret) => { Object.keys(secret).forEach((key) => { if (!ALLOWED_SECRET_TYPES.includes(key)) { // eslint-disable-next-line no-param-reassign delete secret[key]; } }); }); } async synchronizeSecretsToStorage() { try { this.removeInconsistentSecrets(); this.removeUnknownPropertiesFromSecrets(); await writeFile(CACHE_SECRETS_FILE, JSON.stringify(this.secrets)); } catch (err) { this.logger.error("failed to synchronize secrets to storage", err); } } getAllSecrets() { return this.secrets; } }