UNPKG

@updating-secrets/infisical-adapter

Version:

Infisical adapter for the updating-secrets package.

169 lines (168 loc) 6.36 kB
import { check } from '@augment-vir/assert'; import { ensureError, getOrSet, mapObjectValuesSync, parseWithJson5, removePrefix, removeSuffix, stringify, wrapInTry, } from '@augment-vir/common'; import { BaseSecretsAdapter, } from 'updating-secrets'; /** * Loads secrets from Infisical. A `InfisicalSDK` instance must be provided. The `InfisicalSDK` * instance must also be authorized _before_ passing it into this. * * @category Adapters */ export class InfisicalAdapter extends BaseSecretsAdapter { infisicalClient; infisicalEnvironment; constructor( /** * Make sure that you've already authenticated this client. * (`client.auth().universalAuth.login()`) */ infisicalClient, /** `'dev'`, `'staging'`, `'prod'`, etc. */ infisicalEnvironment) { super('InfisicalAdapter'); this.infisicalClient = infisicalClient; this.infisicalEnvironment = infisicalEnvironment; } /** Load secrets from the provided `InfisicalSDK`. */ loadSecrets(secrets) { const secretsCache = {}; return mapObjectValuesSync(secrets, (secretName, secretDefinition) => { const infisicalConfig = secretDefinition.adapterConfig.infisical; if (!infisicalConfig) { return new Error(`No Infisical adapter config (required for using InfisicalAdapter) defined for secret '${secretDefinition.secretName}'.`); } const projectSecretsPromise = getOrSet(secretsCache, infisicalConfig.projectId, () => { try { return (this.infisicalClient .secrets() .listSecrets({ recursive: true, environment: this.infisicalEnvironment, projectId: infisicalConfig.projectId, viewSecretValue: true, }) .then(({ secrets }) => { return mapInfisicalSecrets(secrets); }) /* node:coverage ignore next 3 */ .catch((error) => { return ensureError(error); })); } catch (error) { return Promise.reject(ensureError(error)); } }); return projectSecretsPromise .then((projectSecrets) => { /* node:coverage ignore next 3 */ if (projectSecrets instanceof Error) { return projectSecrets; } const folderValue = infisicalConfig.folderPath === '/' || !infisicalConfig.folderPath ? projectSecrets : getNested(projectSecrets, removeSuffix({ value: removePrefix({ value: infisicalConfig.folderPath, prefix: '/', }), suffix: '/', }).split('/')); /* node:coverage ignore next 3 */ if (check.isError(folderValue)) { return folderValue; } if (infisicalConfig.keyInFolder) { if (check.isString(folderValue)) { throw new TypeError(`Cannot get keyInFolder of a non-folder.`); } else if (check.hasKey(folderValue, infisicalConfig.keyInFolder)) { const value = folderValue[infisicalConfig.keyInFolder]; if (secretDefinition.shapeDefinition && check.isString(value)) { return wrapInTry(() => parseWithJson5(value), { fallbackValue: folderValue, }); } else { return value; } } else { throw new Error(`Secret key not in folder '${infisicalConfig.folderPath}'`); } } else { return folderValue; } }) .catch((error) => { return ensureError(error); }); }); } } function mapInfisicalSecrets(secrets) { const secretsMap = {}; secrets.forEach((secret) => { if (!secret.secretPath || secret.secretPath === '/') { secretsMap[secret.secretKey] = secret.secretValue; } else { setNested(secretsMap, removeSuffix({ value: removePrefix({ value: secret.secretPath, prefix: '/' }), suffix: '/', }) .split('/') .concat(secret.secretKey), secret.secretValue); } }); return secretsMap; } /** * Get an Infisical secret value nested within an object. * * @category Internal */ export function getNested(parent, keys) { const nextKey = keys[0]; if (nextKey == undefined) { throw new Error('Invalid key or ran out of keys.'); } const nextParent = parent[nextKey]; if (!nextParent) { throw new Error(`Nothing at key '${nextKey}'`); } else if (nextParent instanceof Error) { throw nextParent; } if (keys.length > 1) { if (check.isString(nextParent)) { throw new TypeError(`Keys still remain but received string value: ${stringify({ keys })}`); } return getNested(nextParent, keys.slice(1)); } else { return nextParent; } } /** * Set an Infisical secret value nested within an object. * * @category Internal */ export function setNested(parent, keys, value) { const nextKey = keys[0]; if (nextKey == undefined) { throw new Error('Invalid key or ran out of keys.'); } if (keys.length === 1) { parent[nextKey] = value; return; } const nextParent = getOrSet(parent, nextKey, () => { return {}; }); if (check.isString(nextParent) || check.isError(nextParent)) { throw new TypeError(`Cannot set key '${nextKey}'; it's already set to a non-object.`); } return setNested(nextParent, keys.slice(1), value); }