import { getSecrets, getSecret, createSecret, updateSecret, deleteSecret } from '../api'; import { GetFallbackSecretParams, GetDecryptedSecretsParams, GetDecryptedSecretParams, CreateSecretParams, UpdateSecretParams, DeleteSecretParams } from '../types/SecretService'; import { ISecret, ISecretBundle } from '../types/models'; import { decryptSymmetric128BitHexKeyUTF8, encryptSymmetric128BitHexKeyUTF8 } from '../utils/crypto'; /** * Transform a raw secret returned from the API plus [secretName] * and [secretValue] into secret to be returned from SDK. * @param param0 * @returns */ const transformSecretToSecretBundle = ({ secret, secretName, secretValue }: { secret: ISecret; secretName: string; secretValue: string; }): ISecretBundle => ({ secretName, secretValue, version: secret.version, workspace: secret.workspace, environment: secret.environment, type: secret.type, updatedAt: secret.updatedAt, createdAt: secret.createdAt, isFallback: false, lastFetchedAt: new Date() }); /** * Get fallback secret on [process.env] */ export const getFallbackSecretHelper = async ({ secretName }: GetFallbackSecretParams): Promise => ({ secretName, secretValue: process.env[secretName], isFallback: true, lastFetchedAt: new Date() }); /** * Get (decrypted) secrets from a project and environment * @param {GetDecryptedSecretsParams} getDecryptedSecretsParams * @returns {Secret} secrets */ export const getDecryptedSecretsHelper = async ({ apiRequest, workspaceId, environment, workspaceKey, path, includeImports }: GetDecryptedSecretsParams) => { const { secrets, imports } = await getSecrets(apiRequest, { workspaceId, environment, path, includeImports }); const importedSecrets: ISecretBundle[] = [] for (const singleImport of imports) { for (const secret of singleImport.secrets) { const secretName = decryptSymmetric128BitHexKeyUTF8({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, key: workspaceKey }); const secretValue = decryptSymmetric128BitHexKeyUTF8({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, key: workspaceKey }); const transformedSecret = transformSecretToSecretBundle({ secret, secretName, secretValue }); importedSecrets.push(transformedSecret) } } const topLevelSecrets: ISecretBundle[] = secrets.map((secret) => { const secretName = decryptSymmetric128BitHexKeyUTF8({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, key: workspaceKey }); const secretValue = decryptSymmetric128BitHexKeyUTF8({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, key: workspaceKey }); return transformSecretToSecretBundle({ secret, secretName, secretValue }); }); const hasOverridden: any = {}; for (const sec of topLevelSecrets) { hasOverridden[sec.secretName] = true; } // go backwards because the last import overrides any other imports for (let i = importedSecrets.length - 1; i >= 0; i--) { const importedSecret = importedSecrets[i]; if (!hasOverridden[importedSecret.secretName]) { topLevelSecrets.push(importedSecret); hasOverridden[importedSecret.secretName] = true; } } return topLevelSecrets } /** * Get a (decrypted) secret * @param {GetDecryptedSecretParams} getDecryptedSecretParams * @returns */ export const getDecryptedSecretHelper = async ({ apiRequest, secretName, workspaceId, environment, workspaceKey, type, path }: GetDecryptedSecretParams): Promise => { const secret = await getSecret(apiRequest, { secretName, workspaceId, environment, type, path }); const secretValue = decryptSymmetric128BitHexKeyUTF8({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, key: workspaceKey }); return transformSecretToSecretBundle({ secret, secretName, secretValue }); } /** * Create a secret * @param {CreateSecretParams} createSecretParams * @returns */ export const createSecretHelper = async ({ apiRequest, workspaceKey, workspaceId, environment, type, secretName, secretValue, path }: CreateSecretParams) => { const { ciphertext: secretKeyCiphertext, iv: secretKeyIV, tag: secretKeyTag } = encryptSymmetric128BitHexKeyUTF8({ plaintext: secretName, key: workspaceKey }); const { ciphertext: secretValueCiphertext, iv: secretValueIV, tag: secretValueTag } = encryptSymmetric128BitHexKeyUTF8({ plaintext: secretValue, key: workspaceKey }); const secret = await createSecret(apiRequest, { secretName, workspaceId, environment, type, secretKeyCiphertext, secretKeyIV, secretKeyTag, secretValueCiphertext, secretValueIV, secretValueTag, path }); return transformSecretToSecretBundle({ secret, secretName, secretValue }); } /** * Update a secret * @param {UpdateSecretParams} updateSecretParams * @returns */ export const updateSecretHelper = async ({ apiRequest, workspaceKey, workspaceId, environment, type, path, secretName, secretValue }: UpdateSecretParams) => { const { ciphertext: secretValueCiphertext, iv: secretValueIV, tag: secretValueTag } = encryptSymmetric128BitHexKeyUTF8({ plaintext: secretValue, key: workspaceKey }) const secret = await updateSecret(apiRequest, { secretName, workspaceId, environment, type, path, secretValueCiphertext, secretValueIV, secretValueTag }); return transformSecretToSecretBundle({ secret, secretName, secretValue }); } /** * Delete a secret * @param {DeleteSecretParams} deleteSecretParams * @returns */ export const deleteSecretHelper = async ({ apiRequest, secretName, workspaceId, environment, type, path, workspaceKey }: DeleteSecretParams) => { const secret = await deleteSecret(apiRequest, { secretName, workspaceId, environment, type, path }); const secretValue = decryptSymmetric128BitHexKeyUTF8({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, key: workspaceKey }); return transformSecretToSecretBundle({ secret, secretName, secretValue }); }