@shons/next-configify
Version:
NestJS Config on Next Steroids
118 lines (107 loc) • 3.34 kB
text/typescript
import {
GetSecretValueCommand,
SecretsManagerClient,
} from '@aws-sdk/client-secrets-manager';
import { ConfigurationResolver } from '../configuration-resolver.interface';
import { ResolvedValue } from '../resolved-value.interface';
/**
* AWS Secrets Manager configuration resolver.
*/
export class AwsSecretsManagerConfigurationResolver
implements ConfigurationResolver
{
private readonly AWS_SECRETS_AMANGER_YAML_KEY = 'aws-secrets-manager';
private readonly AWS_SECRETS_MANAGER_ENV_PREFIX = 'AWS_SECRETS_MANAGER';
/**
* Creates a new instance of secrets manager configuration resolver
*
* @param {SecretsManagerClient} secretsManager secrets manager client
*/
constructor(private readonly secretsManager: SecretsManagerClient) {}
/**
* Fetches secrets and assign it to an object representation
* of the configuration.
*
* @param {Record<string, any>} config the configuration object
* @returns {Record<string, any>} the cofiguration with secret values assigned
* @throws {Error} if unable to fetch the secret
*/
async resolve(config: Record<string, any>): Promise<Record<string, any>> {
const secrets = this.filterConfiguration(config);
const promises = this.buildBulkRequest(secrets);
const results = await Promise.all(promises);
const errors = results.filter((r) => !r.success);
if (errors && errors.length) {
throw new Error(
`Unable to resolve secrets:\n${errors
.map((e) => `${e.key}: ${e.id} - ${e.error.message}`)
.join('\n')}`,
);
}
for (const result of results) {
config[result.key] = result.value;
}
return config;
}
/**
* Filters the configuration object by aws secrets manager keys.
*
* @param {Record<string, any>} config the configuration object
* @returns {Record<string, any>} the filtered object
*/
private filterConfiguration(
config: Record<string, any>,
): Record<string, any> {
return Object.fromEntries(
Object.entries(config).filter(
([key]) =>
key.startsWith(this.AWS_SECRETS_AMANGER_YAML_KEY) ||
key.startsWith(this.AWS_SECRETS_MANAGER_ENV_PREFIX),
),
);
}
/**
* Resolves secret values.
*
* @param {string} key the object key
* @param {string} id the secret id
* @returns
*/
private async resolveSecretValue(
key: string,
id: string,
): Promise<ResolvedValue> {
try {
const command = new GetSecretValueCommand({ SecretId: id });
const response = await this.secretsManager.send(command);
return {
id,
key,
value: response.SecretString,
success: true,
};
} catch (e) {
return {
id,
key,
error: e,
success: false,
};
}
}
/**
* Creates a list of promises to get secret values.
*
* @param {Record<string, any>} config the configuration object
* @returns {Promise<ResolvedValue>[]} the get secret value promises
*/
private buildBulkRequest(
config: Record<string, any>,
): Promise<ResolvedValue>[] {
const promises = [];
for (const [key, value] of Object.entries(config)) {
promises.push(this.resolveSecretValue(key, value));
}
return promises;
}
}