UNPKG

aws-secret-manager-client

Version:

Modern AWS Secrets Manager client for Node.js with TypeScript support, built on AWS SDK v3

1 lines 17.9 kB
{"version":3,"sources":["../src/SecretsManagerClient.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/explicit-function-return-type */\nimport {\n SecretsManagerClient as AwsSecretsManagerClient,\n GetSecretValueCommand,\n CreateSecretCommand,\n UpdateSecretCommand,\n DeleteSecretCommand,\n RotateSecretCommand,\n CancelRotateSecretCommand,\n SecretsManagerClientConfig,\n GetSecretValueCommandInput,\n CreateSecretCommandInput,\n UpdateSecretCommandInput,\n DeleteSecretCommandInput,\n RotateSecretCommandInput,\n CancelRotateSecretCommandInput,\n} from '@aws-sdk/client-secrets-manager';\nimport type { Logger } from '@aws-sdk/types';\n\nexport interface SecretsManagerOptions extends SecretsManagerClientConfig {\n retryPolicy?: {\n maxRetries?: number;\n retryDelayOptions?: {\n base?: number;\n customBackoff?: (retryCount: number) => number;\n };\n };\n timeout?: number;\n logger?: Logger;\n}\n\nexport interface SecretValue {\n arn?: string | undefined;\n name?: string | undefined;\n value?: string | undefined;\n versionId?: string | undefined;\n versionStages?: string[] | undefined;\n createdDate?: Date | undefined;\n}\n\nexport class SecretsManagerError extends Error {\n constructor(\n message: string,\n public readonly code?: string,\n public readonly statusCode?: number,\n public readonly originalError?: Error\n ) {\n super(message);\n this.name = 'SecretsManagerError';\n }\n}\n\nexport class SecretsManagerClient {\n private awsClient: AwsSecretsManagerClient;\n private options: SecretsManagerOptions;\n private logger: Required<SecretsManagerOptions['logger']>;\n\n constructor(options: SecretsManagerOptions = {}) {\n this.options = {\n region: process.env.AWS_REGION || 'us-east-1',\n ...options,\n };\n\n // Initialize logger with default no-op functions\n this.logger = {\n trace: options.logger?.trace || (() => {}),\n debug: options.logger?.debug || (() => {}),\n info: options.logger?.info || (() => {}),\n warn: options.logger?.warn || (() => {}),\n error: options.logger?.error || (() => {}),\n };\n\n // Create AWS SDK v3 client\n this.awsClient = new AwsSecretsManagerClient({\n ...this.options,\n region: this.options.region ?? 'us-east-1',\n maxAttempts: this.options.retryPolicy?.maxRetries || 3,\n });\n\n this.logger.info('SecretsManager client initialized', {\n region: this.options.region,\n maxRetries: this.options.retryPolicy?.maxRetries || 3,\n });\n }\n\n /**\n * Validates that a secret ID is provided and not empty\n */\n private validateSecretId(secretId: string): void {\n if (!secretId || typeof secretId !== 'string' || secretId.trim() === '') {\n throw new SecretsManagerError('SecretId is required and must be a non-empty string');\n }\n }\n\n /**\n * Handles AWS SDK errors and converts them to SecretsManagerError\n */\n private handleError(error: any, operation: string): never {\n (this.logger?.error ?? (() => {}))(`Error in ${operation}:`, error ?? {});\n\n if (error.name === 'ResourceNotFoundException') {\n throw new SecretsManagerError(\n `Secret not found: ${error.message}`,\n 'RESOURCE_NOT_FOUND',\n 404,\n error\n );\n }\n\n if (error.name === 'AccessDeniedError') {\n throw new SecretsManagerError(`Access denied: ${error.message}`, 'ACCESS_DENIED', 403, error);\n }\n\n if (error.name === 'InvalidParameterException') {\n throw new SecretsManagerError(\n `Invalid parameter: ${error.message}`,\n 'INVALID_PARAMETER',\n 400,\n error\n );\n }\n\n if (error.name === 'InvalidRequestException') {\n throw new SecretsManagerError(\n `Invalid request: ${error.message}`,\n 'INVALID_REQUEST',\n 400,\n error\n );\n }\n\n if (error.name === 'ResourceExistsException') {\n throw new SecretsManagerError(\n `Resource already exists: ${error.message}`,\n 'RESOURCE_EXISTS',\n 409,\n error\n );\n }\n\n // Generic error handling\n throw new SecretsManagerError(\n `Operation ${operation} failed: ${error.message || 'Unknown error'}`,\n error.name || 'UNKNOWN_ERROR',\n error.$metadata?.httpStatusCode || 500,\n error\n );\n }\n\n /**\n * Retrieves a secret value from AWS Secrets Manager\n */\n async getSecret(\n secretId: string,\n versionId?: string,\n versionStage?: string\n ): Promise<SecretValue> {\n this.validateSecretId(secretId);\n\n const params: GetSecretValueCommandInput = {\n SecretId: secretId,\n ...(versionId && { VersionId: versionId }),\n ...(versionStage && { VersionStage: versionStage }),\n };\n\n this.logger?.debug('Getting secret', { secretId, versionId, versionStage });\n\n try {\n const command = new GetSecretValueCommand(params);\n const response = await this.awsClient.send(command);\n\n const secretValue: SecretValue = {\n arn: response.ARN,\n name: response.Name,\n value: response.SecretString,\n versionId: response.VersionId,\n versionStages: response.VersionStages,\n createdDate: response.CreatedDate,\n };\n\n this.logger?.info('Secret retrieved successfully', { secretId });\n return secretValue;\n } catch (error) {\n this.handleError(error, 'getSecret');\n }\n }\n\n /**\n * Retrieves and parses a JSON secret value\n */\n async getSecretJson<T = unknown>(\n secretId: string,\n versionId?: string,\n versionStage?: string\n ): Promise<T> {\n const secret = await this.getSecret(secretId, versionId, versionStage);\n\n if (!secret.value) {\n throw new SecretsManagerError('Secret value is empty', 'EMPTY_SECRET_VALUE');\n }\n\n try {\n return JSON.parse(secret.value) as T;\n } catch (error) {\n throw new SecretsManagerError(\n 'Failed to parse secret value as JSON',\n 'INVALID_JSON',\n 400,\n error as Error\n );\n }\n }\n\n /**\n * Creates a new secret in AWS Secrets Manager\n */\n async createSecret(\n params: CreateSecretCommandInput\n ): Promise<{ arn: string; name: string; versionId: string }> {\n if (!params.Name || typeof params.Name !== 'string') {\n throw new SecretsManagerError('Name is required and must be a string');\n }\n\n this.logger?.debug('Creating secret', { name: params.Name });\n\n try {\n const command = new CreateSecretCommand(params);\n const response = await this.awsClient.send(command);\n\n this.logger?.info('Secret created successfully', {\n name: params.Name,\n arn: response.ARN,\n });\n\n return {\n arn: response.ARN ?? '',\n name: response.Name ?? '',\n versionId: response.VersionId ?? '',\n };\n } catch (error) {\n this.handleError(error, 'createSecret');\n }\n }\n\n /**\n * Updates an existing secret in AWS Secrets Manager\n */\n async updateSecret(\n params: UpdateSecretCommandInput\n ): Promise<{ arn: string; name: string; versionId: string }> {\n if (!params.SecretId || typeof params.SecretId !== 'string') {\n throw new SecretsManagerError('SecretId is required and must be a string');\n }\n\n this.logger?.debug('Updating secret', { secretId: params.SecretId });\n\n try {\n const command = new UpdateSecretCommand(params);\n const response = await this.awsClient.send(command);\n\n this.logger?.info('Secret updated successfully', {\n secretId: params.SecretId,\n arn: response.ARN,\n });\n\n return {\n arn: response.ARN ?? '',\n name: response.Name ?? '',\n versionId: response.VersionId ?? '',\n };\n } catch (error) {\n this.handleError(error, 'updateSecret');\n }\n }\n\n /**\n * Deletes a secret from AWS Secrets Manager\n */\n async deleteSecret(\n secretId: string,\n forceDeleteWithoutRecovery = false,\n recoveryWindowInDays?: number\n ): Promise<{ arn: string; name: string; deletionDate: Date }> {\n this.validateSecretId(secretId);\n\n const params: DeleteSecretCommandInput = {\n SecretId: secretId,\n ForceDeleteWithoutRecovery: forceDeleteWithoutRecovery,\n ...(recoveryWindowInDays && { RecoveryWindowInDays: recoveryWindowInDays }),\n };\n\n this.logger?.debug('Deleting secret', { secretId, forceDeleteWithoutRecovery });\n\n try {\n const command = new DeleteSecretCommand(params);\n const response = await this.awsClient.send(command);\n\n this.logger?.info('Secret deleted successfully', { secretId });\n\n return {\n arn: response.ARN ?? '',\n name: response.Name ?? '',\n deletionDate: response.DeletionDate\n ? typeof response.DeletionDate === 'string'\n ? new Date(response.DeletionDate)\n : response.DeletionDate\n : new Date(0),\n };\n } catch (error) {\n this.handleError(error, 'deleteSecret');\n }\n }\n\n /**\n * Rotates a secret in AWS Secrets Manager\n */\n async rotateSecret(\n params: RotateSecretCommandInput\n ): Promise<{ arn: string; name: string; versionId: string }> {\n if (!params.SecretId || typeof params.SecretId !== 'string') {\n throw new SecretsManagerError('SecretId is required and must be a string');\n }\n\n this.logger?.debug('Rotating secret', { secretId: params.SecretId });\n\n try {\n const command = new RotateSecretCommand(params);\n const response = await this.awsClient.send(command);\n\n this.logger?.info('Secret rotation initiated', { secretId: params.SecretId });\n\n return {\n arn: response.ARN ?? '',\n name: response.Name ?? '',\n versionId: response.VersionId ?? '',\n };\n } catch (error) {\n this.handleError(error, 'rotateSecret');\n }\n }\n\n /**\n * Cancels secret rotation in AWS Secrets Manager\n */\n async cancelRotateSecret(\n secretId: string\n ): Promise<{ arn: string; name: string; versionId: string }> {\n this.validateSecretId(secretId);\n\n const params: CancelRotateSecretCommandInput = {\n SecretId: secretId,\n };\n\n this.logger?.debug('Canceling secret rotation', { secretId });\n\n try {\n const command = new CancelRotateSecretCommand(params);\n const response = await this.awsClient.send(command);\n\n this.logger?.info('Secret rotation canceled', { secretId });\n\n return {\n arn: response.ARN ?? '',\n name: response.Name ?? '',\n versionId: response.VersionId ?? '',\n };\n } catch (error) {\n this.handleError(error, 'cancelRotateSecret');\n }\n }\n\n /**\n * Health check method to verify the client can connect to AWS\n */\n async healthCheck(): Promise<{ status: 'healthy' | 'unhealthy'; message: string }> {\n try {\n // Try to list secrets (this requires minimal permissions)\n const command = new GetSecretValueCommand({ SecretId: 'health-check-non-existent-secret' });\n await this.awsClient.send(command);\n\n return { status: 'healthy', message: 'Client is healthy' };\n } catch (error) {\n // If we get a ResourceNotFoundException, that's actually good - it means we can connect\n if ((error as { name?: string }).name === 'ResourceNotFoundException') {\n return { status: 'healthy', message: 'Client is healthy' };\n }\n\n // Any other error indicates a problem\n return {\n status: 'unhealthy',\n message: `Health check failed: ${(error as { message?: string }).message}`,\n };\n }\n }\n\n /**\n * Closes the client connection\n */\n async close(): Promise<void> {\n this.logger?.info('Closing SecretsManager client');\n await this.awsClient.destroy();\n }\n}\n\n// Export the class as default\nexport default SecretsManagerClient;\n"],"mappings":"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","names":["AwsSecretsManagerClient","GetSecretValueCommand","CreateSecretCommand","UpdateSecretCommand","DeleteSecretCommand","RotateSecretCommand","CancelRotateSecretCommand","SecretsManagerError","message","code","statusCode","originalError","SecretsManagerClient","options","_a","_b","_c","_d","_e","_f","_g","secretId","error","operation","versionId","versionStage","params","command","response","secretValue","secret","forceDeleteWithoutRecovery","recoveryWindowInDays"]}