GOOD SHELL MAS BOY
Server: Apache/2.4.52 (Ubuntu)
System: Linux vmi1836763.contaboserver.net 5.15.0-130-generic #140-Ubuntu SMP Wed Dec 18 17:59:53 UTC 2024 x86_64
User: www-data (33)
PHP: 8.4.10
Disabled: NONE
Upload Files
File: //usr/local/lib/node_modules/firebase-tools/lib/functions/secrets.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.describeSecret = exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.versionInUse = exports.inUse = exports.getSecretVersions = exports.of = exports.ensureSecret = exports.ensureValidKey = void 0;
const utils = require("../utils");
const poller = require("../operation-poller");
const gcfV1 = require("../gcp/cloudfunctions");
const gcfV2 = require("../gcp/cloudfunctionsv2");
const api_1 = require("../api");
const secretManager_1 = require("../gcp/secretManager");
const error_1 = require("../error");
const utils_1 = require("../utils");
const prompt_1 = require("../prompt");
const env_1 = require("./env");
const logger_1 = require("../logger");
const functional_1 = require("../functional");
const secretManager_2 = require("../gcp/secretManager");
const secretManager_3 = require("../gcp/secretManager");
const projectUtils_1 = require("../projectUtils");
const Table = require("cli-table");
const gcfV1PollerOptions = {
    apiOrigin: (0, api_1.functionsOrigin)(),
    apiVersion: "v1",
    masterTimeout: 25 * 60 * 1000,
    maxBackoff: 10000,
};
const gcfV2PollerOptions = {
    apiOrigin: (0, api_1.functionsV2Origin)(),
    apiVersion: "v2",
    masterTimeout: 25 * 60 * 1000,
    maxBackoff: 10000,
};
function toUpperSnakeCase(key) {
    return key
        .replace(/[.-]/g, "_")
        .replace(/([a-z])([A-Z])/g, "$1_$2")
        .toUpperCase();
}
async function ensureValidKey(key, options) {
    const transformedKey = toUpperSnakeCase(key);
    if (transformedKey !== key) {
        if (options.force) {
            throw new error_1.FirebaseError("Secret key must be in UPPER_SNAKE_CASE.");
        }
        (0, utils_1.logWarning)(`By convention, secret key must be in UPPER_SNAKE_CASE.`);
        const confirm = await (0, prompt_1.promptOnce)({
            name: "updateKey",
            type: "confirm",
            default: true,
            message: `Would you like to use ${transformedKey} as key instead?`,
        }, options);
        if (!confirm) {
            throw new error_1.FirebaseError("Secret key must be in UPPER_SNAKE_CASE.");
        }
    }
    try {
        (0, env_1.validateKey)(transformedKey);
    }
    catch (err) {
        throw new error_1.FirebaseError(`Invalid secret key ${transformedKey}`, { children: [err] });
    }
    return transformedKey;
}
exports.ensureValidKey = ensureValidKey;
async function ensureSecret(projectId, name, options) {
    try {
        const secret = await (0, secretManager_1.getSecret)(projectId, name);
        if ((0, secretManager_1.isAppHostingManaged)(secret)) {
            (0, utils_1.logWarning)("Your secret is managed by Firebase App Hosting. Continuing will disable automatic deletion of old versions.");
            const stopTracking = await (0, prompt_1.promptOnce)({
                name: "doNotTrack",
                type: "confirm",
                default: false,
                message: "Do you wish to continue?",
            }, options);
            if (stopTracking) {
                delete secret.labels[secretManager_2.FIREBASE_MANAGED];
                await (0, secretManager_1.patchSecret)(secret.projectId, secret.name, secret.labels);
            }
            else {
                throw new Error("A secret cannot be managed by both Firebase App Hosting and Cloud Functions for Firebase");
            }
        }
        else if (!(0, secretManager_2.isFunctionsManaged)(secret)) {
            if (!options.force) {
                (0, utils_1.logWarning)("Your secret is not managed by Cloud Functions for Firebase. " +
                    "Firebase managed secrets are automatically pruned to reduce your monthly cost for using Secret Manager. ");
                const confirm = await (0, prompt_1.promptOnce)({
                    name: "updateLabels",
                    type: "confirm",
                    default: true,
                    message: `Would you like to have your secret ${secret.name} managed by Cloud Functions for Firebase?`,
                }, options);
                if (confirm) {
                    return (0, secretManager_1.patchSecret)(projectId, secret.name, Object.assign(Object.assign({}, secret.labels), (0, secretManager_3.labels)()));
                }
            }
        }
        return secret;
    }
    catch (err) {
        if (err.status !== 404) {
            throw err;
        }
    }
    return await (0, secretManager_1.createSecret)(projectId, name, (0, secretManager_3.labels)());
}
exports.ensureSecret = ensureSecret;
function of(endpoints) {
    return endpoints.reduce((envs, endpoint) => [...envs, ...(endpoint.secretEnvironmentVariables || [])], []);
}
exports.of = of;
function getSecretVersions(endpoint) {
    return (endpoint.secretEnvironmentVariables || []).reduce((memo, { secret, version }) => {
        memo[secret] = version || "";
        return memo;
    }, {});
}
exports.getSecretVersions = getSecretVersions;
function inUse(projectInfo, secret, endpoint) {
    const { projectId, projectNumber } = projectInfo;
    for (const sev of of([endpoint])) {
        if ((sev.projectId === projectId || sev.projectId === projectNumber) &&
            sev.secret === secret.name) {
            return true;
        }
    }
    return false;
}
exports.inUse = inUse;
function versionInUse(projectInfo, sv, endpoint) {
    const { projectId, projectNumber } = projectInfo;
    for (const sev of of([endpoint])) {
        if ((sev.projectId === projectId || sev.projectId === projectNumber) &&
            sev.secret === sv.secret.name &&
            sev.version === sv.versionId) {
            return true;
        }
    }
    return false;
}
exports.versionInUse = versionInUse;
async function pruneSecrets(projectInfo, endpoints) {
    const { projectId, projectNumber } = projectInfo;
    const pruneKey = (name, version) => `${name}@${version}`;
    const prunedSecrets = new Set();
    const haveSecrets = await (0, secretManager_1.listSecrets)(projectId, `labels.${secretManager_2.FIREBASE_MANAGED}=true`);
    for (const secret of haveSecrets) {
        const versions = await (0, secretManager_1.listSecretVersions)(projectId, secret.name, `NOT state: DESTROYED`);
        for (const version of versions) {
            prunedSecrets.add(pruneKey(secret.name, version.versionId));
        }
    }
    const secrets = [];
    for (const secret of of(endpoints)) {
        if (!secret.version) {
            throw new error_1.FirebaseError(`Secret ${secret.secret} version is unexpectedly empty.`);
        }
        if (secret.projectId === projectId || secret.projectId === projectNumber) {
            if (secret.version) {
                secrets.push(Object.assign(Object.assign({}, secret), { version: secret.version }));
            }
        }
    }
    for (const sev of secrets) {
        let name = sev.secret;
        if (name.includes("/")) {
            const secret = (0, secretManager_1.parseSecretResourceName)(name);
            name = secret.name;
        }
        let version = sev.version;
        if (version === "latest") {
            const resolved = await (0, secretManager_1.getSecretVersion)(projectId, name, version);
            version = resolved.versionId;
        }
        prunedSecrets.delete(pruneKey(name, version));
    }
    return Array.from(prunedSecrets)
        .map((key) => key.split("@"))
        .map(([secret, version]) => ({ projectId, version, secret, key: secret }));
}
exports.pruneSecrets = pruneSecrets;
async function pruneAndDestroySecrets(projectInfo, endpoints) {
    const { projectId, projectNumber } = projectInfo;
    logger_1.logger.debug("Pruning secrets to find unused secret versions...");
    const unusedSecrets = await module.exports.pruneSecrets({ projectId, projectNumber }, endpoints);
    if (unusedSecrets.length === 0) {
        return { destroyed: [], erred: [] };
    }
    const destroyed = [];
    const erred = [];
    const msg = unusedSecrets.map((s) => `${s.secret}@${s.version}`);
    logger_1.logger.debug(`Found unused secret versions: ${msg}. Destroying them...`);
    const destroyResults = await utils.allSettled(unusedSecrets.map(async (sev) => {
        await (0, secretManager_1.destroySecretVersion)(sev.projectId, sev.secret, sev.version);
        return sev;
    }));
    for (const result of destroyResults) {
        if (result.status === "fulfilled") {
            destroyed.push(result.value);
        }
        else {
            erred.push(result.reason);
        }
    }
    return { destroyed, erred };
}
exports.pruneAndDestroySecrets = pruneAndDestroySecrets;
async function updateEndpointSecret(projectInfo, secretVersion, endpoint) {
    const { projectId, projectNumber } = projectInfo;
    if (!inUse(projectInfo, secretVersion.secret, endpoint)) {
        return endpoint;
    }
    const updatedSevs = [];
    for (const sev of of([endpoint])) {
        const updatedSev = Object.assign({}, sev);
        if ((updatedSev.projectId === projectId || updatedSev.projectId === projectNumber) &&
            updatedSev.secret === secretVersion.secret.name) {
            updatedSev.version = secretVersion.versionId;
        }
        updatedSevs.push(updatedSev);
    }
    if (endpoint.platform === "gcfv1") {
        const fn = gcfV1.functionFromEndpoint(endpoint, "");
        const op = await gcfV1.updateFunction({
            name: fn.name,
            runtime: fn.runtime,
            entryPoint: fn.entryPoint,
            secretEnvironmentVariables: updatedSevs,
        });
        const cfn = await poller.pollOperation(Object.assign(Object.assign({}, gcfV1PollerOptions), { operationResourceName: op.name }));
        return gcfV1.endpointFromFunction(cfn);
    }
    else if (endpoint.platform === "gcfv2") {
        const fn = gcfV2.functionFromEndpoint(endpoint);
        const op = await gcfV2.updateFunction(Object.assign(Object.assign({}, fn), { serviceConfig: Object.assign(Object.assign({}, fn.serviceConfig), { secretEnvironmentVariables: updatedSevs }) }));
        const cfn = await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { operationResourceName: op.name }));
        return gcfV2.endpointFromFunction(cfn);
    }
    else {
        (0, functional_1.assertExhaustive)(endpoint.platform);
    }
}
exports.updateEndpointSecret = updateEndpointSecret;
async function describeSecret(key, options) {
    const projectId = (0, projectUtils_1.needProjectId)(options);
    const versions = await (0, secretManager_1.listSecretVersions)(projectId, key);
    const table = new Table({
        head: ["Version", "State"],
        style: { head: ["yellow"] },
    });
    for (const version of versions) {
        table.push([version.versionId, version.state]);
    }
    logger_1.logger.info(table.toString());
    return { secrets: versions };
}
exports.describeSecret = describeSecret;