@sap/cli-core
Version:
Command-Line Interface (CLI) Core Module
143 lines (142 loc) • 5.08 kB
JavaScript
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;
}
}