UNPKG

@freakyfelt/secrets-js

Version:

Client library to make secrets fetching easy and safe

1 lines 16.6 kB
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/utils/secret-content.ts","../src/utils/secret-copier.ts","../src/secret-value.ts","../src/fetcher.ts"],"sourcesContent":["export * from \"./errors.ts\";\nexport * from \"./fetcher.ts\";\nexport * from \"./secret-value.ts\";\nexport * from \"./types.ts\";\n","export class SecretsError extends Error {\n constructor(\n msg: string,\n public readonly details: object,\n ) {\n super(msg);\n }\n}\n\n/**\n * A class of errors around a specific SecretValue\n */\nexport class InvalidSecretError extends SecretsError {\n constructor(msg: string, arn: string | null) {\n super(msg, { arn });\n }\n}\n\n/**\n * Thrown when a parser exception was thrown while parsing the secret\n */\nexport class SecretParseError extends InvalidSecretError {}\n\n/**\n * Thrown when the requested operation is not supported\n */\nexport class UnsupportedOperationError extends InvalidSecretError {}\n","import { GetSecretValueResponse } from \"@aws-sdk/client-secrets-manager\";\nimport { InvalidSecretError } from \"../errors.ts\";\n\nexport type SecretString = {\n type: \"string\";\n SecretString: string;\n};\n\nexport type SecretBinary = {\n type: \"binary\";\n SecretBinary: Uint8Array;\n};\n\nexport type SecretContent = SecretString | SecretBinary;\n\n/**\n * Converts the secret response into a union type for easier type checking\n */\nexport function getSecretContent(\n input: Pick<GetSecretValueResponse, \"ARN\" | \"SecretString\" | \"SecretBinary\">,\n): SecretContent | null {\n if (typeof input.SecretString === \"string\") {\n if (typeof input.SecretBinary !== \"undefined\") {\n throw new InvalidSecretError(\n \"Both SecretString and SecretBinary defined\",\n input.ARN ?? null,\n );\n }\n return {\n type: \"string\",\n SecretString: input.SecretString,\n };\n } else if (typeof input.SecretBinary !== \"undefined\") {\n return {\n type: \"binary\",\n SecretBinary: input.SecretBinary,\n };\n } else {\n return null;\n }\n}\n","import {\n GetSecretValueCommandOutput,\n GetSecretValueResponse,\n} from \"@aws-sdk/client-secrets-manager\";\n\nexport function deepCopySecretValueCommandOutput(\n input: GetSecretValueResponse | GetSecretValueCommandOutput,\n): GetSecretValueCommandOutput {\n const { CreatedDate, SecretBinary, VersionStages, $metadata, ...rest } =\n input as GetSecretValueCommandOutput;\n\n const res = { ...rest } as GetSecretValueCommandOutput;\n if (CreatedDate instanceof Date) {\n res[\"CreatedDate\"] = new Date(CreatedDate);\n }\n if (typeof SecretBinary !== \"undefined\") {\n res[\"SecretBinary\"] = Buffer.from(SecretBinary);\n }\n if (Array.isArray(VersionStages)) {\n res[\"VersionStages\"] = [...VersionStages];\n }\n if (typeof $metadata !== \"undefined\") {\n res[\"$metadata\"] = { ...$metadata };\n }\n\n return res;\n}\n","import type { GetSecretValueResponse } from \"@aws-sdk/client-secrets-manager\";\nimport {\n InvalidSecretError,\n SecretParseError,\n UnsupportedOperationError,\n} from \"./errors.ts\";\nimport type { GetSecretValueMetadata, SecretPayloadType } from \"./types.ts\";\nimport { getSecretContent, SecretContent } from \"./utils/secret-content.ts\";\nimport { deepCopySecretValueCommandOutput } from \"./utils/secret-copier.ts\";\n\nexport class SecretValue {\n constructor(input: GetSecretValueResponse) {\n this.#input = deepCopySecretValueCommandOutput(input);\n this.#arn = input.ARN ?? null;\n this.#content = getSecretContent(input);\n }\n\n #input: GetSecretValueResponse;\n #arn: string | null;\n #content: SecretContent | null;\n\n /**\n * The ARN of the secret\n */\n get ARN(): string {\n return this.#mustGetInputString(\"ARN\");\n }\n\n /**\n * The user-specified friendly name of the secret\n */\n get Name(): string {\n return this.#mustGetInputString(\"Name\");\n }\n\n /**\n * The AWS-specified VersionId of the secret\n */\n get VersionId(): string {\n return this.#mustGetInputString(\"VersionId\");\n }\n\n /**\n * The array of version stages associated with the secret\n *\n * See also {@link isAWSCurrentVersion} and {@link hasVersionStage}\n */\n get VersionStages(): string[] {\n return [\n ...this.#unsafeMustGetInputField(\"VersionStages\", (value) =>\n Array.isArray(value),\n ),\n ];\n }\n\n get CreatedDate(): Date {\n const createdDate = this.#unsafeMustGetInputField(\n \"CreatedDate\",\n (value) => value instanceof Date,\n );\n\n return new Date(createdDate);\n }\n\n /**\n * True if the secret is the current version per AWS (via the AWSCURRENT version stage)\n */\n isAWSCurrentVersion(): boolean {\n return this.hasVersionStage(\"AWSCURRENT\");\n }\n\n /**\n * True if the secret is the upcoming/pending version per AWS (via the AWSPENDING version stage)\n */\n isAWSPendingVersion(): boolean {\n return this.hasVersionStage(\"AWSPENDING\");\n }\n\n /**\n * True if the secret is the previous/outgoing version per AWS (via the AWSPREVIOUS version stage)\n */\n isAWSPreviousVersion(): boolean {\n return this.hasVersionStage(\"AWSPREVIOUS\");\n }\n\n /**\n * Checks if the secret has the specified version stage\n *\n * @param stage The stage name to search for\n * @returns True if the secret has the specified version stage\n */\n hasVersionStage(stage: string): boolean {\n return this.#input.VersionStages?.includes(stage) ?? false;\n }\n\n /**\n * Whether the secret payload was created a string (present as SecretString) or binary (present as SecretBinary)\n */\n get payloadType(): SecretPayloadType {\n if (this.#content === null) {\n throw new InvalidSecretError(\"Invalid content payload\", this.#arn);\n }\n\n return this.#content.type;\n }\n\n /**\n * Returns non-sensitive metadata about the secret\n *\n * Current fields:\n *\n * - ARN\n * - Name\n * - VersionId\n * - VersionStages\n * - CreatedDate\n */\n metadata(): GetSecretValueMetadata {\n const { ARN, CreatedDate, Name, VersionId, VersionStages } = this.#input;\n\n return deepCopySecretValueCommandOutput({\n ARN,\n Name,\n VersionId,\n VersionStages,\n CreatedDate,\n });\n }\n\n /**\n * Converts the SecretString or SecretBinary content into a Buffer\n */\n async bytes(): Promise<Uint8Array> {\n if (this.#content === null) {\n throw new InvalidSecretError(\"Invalid content payload\", this.#arn);\n }\n\n switch (this.#content.type) {\n case \"binary\":\n return Buffer.from(this.#content.SecretBinary);\n case \"string\":\n return Buffer.from(this.#content.SecretString);\n }\n }\n\n /**\n * Parses the contents of SecretString as JSON, throwing a SecretParseError on failure to do so\n *\n * NOTE: The JSON string is always re-parsed on each request\n *\n * @throws {UnsupportedOperationError} the SecretString field is not populated\n * @throws {SecretParseError} the contents of SecretString is not valid JSON\n */\n async json(): Promise<unknown> {\n const str = this.#mustGetSecretString();\n\n try {\n // WARNING: Always re-parse the JSON string to ensure it is not poisoned\n return JSON.parse(str);\n } catch (err: unknown) {\n throw new SecretParseError(\"Could not parse secret as JSON\", this.#arn);\n }\n }\n\n /**\n * Returns the raw response body from the GetSecretValue API call\n *\n * NOTE: The API response will usually be a {@link GetSecretCommandOutput} that implements GetSecretValueResponse\n */\n async raw(): Promise<GetSecretValueResponse> {\n return deepCopySecretValueCommandOutput(this.#input);\n }\n\n /**\n * Returns a string with the contents of `SecretString`\n */\n async text(): Promise<string> {\n return this.#mustGetSecretString();\n }\n\n /**\n * Calls {@link #mustGetInputField}, ensures the value is a string, and returns it\n *\n * NOTE: Strings are immutable, so we can safely return the instance of the field.\n *\n * @param fieldName The name of the field on GetSecretValueResponse to fetch\n * @returns the string value of the field\n */\n #mustGetInputString(fieldName: keyof GetSecretValueResponse): string {\n return this.#unsafeMustGetInputField(\n fieldName,\n (value) => typeof value === \"string\",\n );\n }\n\n /**\n * Fetches the value of a field from the input object, ensuring it is of the expected type.\n *\n * WARNING: unsafe because this returns the instance of the field. Consumers must create a copy of the value before returning it.\n *\n * @param fieldName The name of the field on GetSecretValueResponse to fetch\n * @param typeVerifier A function that verifies the type of the field value\n * @returns The value of the field, cast to the expected type\n */\n #unsafeMustGetInputField<T>(\n fieldName: keyof GetSecretValueResponse,\n typeVerifier: (value: unknown) => value is T,\n ): T {\n if (!Object.hasOwn(this.#input, fieldName)) {\n throw new InvalidSecretError(\n `Missing ${fieldName} in response`,\n this.#arn,\n );\n }\n\n const value = this.#input[fieldName];\n if (!typeVerifier(value)) {\n throw new InvalidSecretError(\n `Invalid ${fieldName} in response`,\n this.#arn,\n );\n }\n\n return value;\n }\n\n /**\n * Fetches the value of the SecretString field or throws UnsupportedOperationError if the secret is binary\n *\n * @throws UnsupportedOperationError if the secret is binary\n */\n #mustGetSecretString(): string {\n if (this.#content?.type !== \"string\") {\n throw new UnsupportedOperationError(\n \"Cannot convert binary secrets to text\",\n this.#arn,\n );\n }\n\n return this.#content.SecretString;\n }\n\n /**\n * Custom inspect method to show only the following information in console.log() calls:\n *\n * - Name\n * - VersionId\n *\n * The output will also indicate the content type (\"string\", \"binary\") in parenthesis\n */\n [Symbol.for(\"nodejs.util.inspect.custom\")](\n depth: number,\n options: any,\n inspect: any,\n ) {\n const contentType = this.#content?.type ?? \"unknown\";\n if (depth <= 0) {\n return options.stylize(`[SecretValue(${contentType})]`, \"special\");\n }\n\n const newOptions = Object.assign({}, options, {\n depth: options.depth === null ? null : options.depth - 1,\n });\n\n const { Name, VersionId } = this.#input;\n const inner = inspect({ Name, VersionId }, newOptions);\n\n return `${options.stylize(`SecretValue(${contentType})`, \"special\")} ${inner}`;\n }\n}\n","import type { GetSecretValueRequest } from \"@aws-sdk/client-secrets-manager\";\nimport { SecretValue } from \"./secret-value.ts\";\nimport type { SecretsManager } from \"./types.ts\";\n\nexport type FetchOptions = Omit<GetSecretValueRequest, \"SecretId\">;\n\nexport class SecretsFetcher {\n constructor(private client: SecretsManager) {}\n\n /**\n * Shorthand method for fetching the string representation of the SecretString\n *\n * @param input\n * @returns the resolved secret\n */\n async fetchString(SecretId: string, opts?: FetchOptions): Promise<string> {\n const res = await this.fetch(SecretId, opts);\n\n return res.text();\n }\n\n /**\n * Shorthand method for fetching the JSON representation of the SecretString\n */\n async fetchJson(SecretId: string, opts?: FetchOptions): Promise<unknown> {\n const res = await this.fetch(SecretId, opts);\n\n return res.json();\n }\n\n async fetch(SecretId: string, opts?: FetchOptions): Promise<SecretValue> {\n const res = await this.client.getSecretValue({\n ...opts,\n SecretId,\n });\n\n return new SecretValue(res);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,eAAN,cAA2B,MAAM;AAAA,EACtC,YACE,KACgB,SAChB;AACA,UAAM,GAAG;AAFO;AAAA,EAGlB;AACF;AAKO,IAAM,qBAAN,cAAiC,aAAa;AAAA,EACnD,YAAY,KAAa,KAAoB;AAC3C,UAAM,KAAK,EAAE,IAAI,CAAC;AAAA,EACpB;AACF;AAKO,IAAM,mBAAN,cAA+B,mBAAmB;AAAC;AAKnD,IAAM,4BAAN,cAAwC,mBAAmB;AAAC;;;ACR5D,SAAS,iBACd,OACsB;AACtB,MAAI,OAAO,MAAM,iBAAiB,UAAU;AAC1C,QAAI,OAAO,MAAM,iBAAiB,aAAa;AAC7C,YAAM,IAAI;AAAA,QACR;AAAA,QACA,MAAM,OAAO;AAAA,MACf;AAAA,IACF;AACA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,cAAc,MAAM;AAAA,IACtB;AAAA,EACF,WAAW,OAAO,MAAM,iBAAiB,aAAa;AACpD,WAAO;AAAA,MACL,MAAM;AAAA,MACN,cAAc,MAAM;AAAA,IACtB;AAAA,EACF,OAAO;AACL,WAAO;AAAA,EACT;AACF;;;ACnCO,SAAS,iCACd,OAC6B;AAC7B,QAAM,EAAE,aAAa,cAAc,eAAe,WAAW,GAAG,KAAK,IACnE;AAEF,QAAM,MAAM,EAAE,GAAG,KAAK;AACtB,MAAI,uBAAuB,MAAM;AAC/B,QAAI,aAAa,IAAI,IAAI,KAAK,WAAW;AAAA,EAC3C;AACA,MAAI,OAAO,iBAAiB,aAAa;AACvC,QAAI,cAAc,IAAI,OAAO,KAAK,YAAY;AAAA,EAChD;AACA,MAAI,MAAM,QAAQ,aAAa,GAAG;AAChC,QAAI,eAAe,IAAI,CAAC,GAAG,aAAa;AAAA,EAC1C;AACA,MAAI,OAAO,cAAc,aAAa;AACpC,QAAI,WAAW,IAAI,EAAE,GAAG,UAAU;AAAA,EACpC;AAEA,SAAO;AACT;;;AChBO,IAAM,cAAN,MAAkB;AAAA,EACvB,YAAY,OAA+B;AACzC,SAAK,SAAS,iCAAiC,KAAK;AACpD,SAAK,OAAO,MAAM,OAAO;AACzB,SAAK,WAAW,iBAAiB,KAAK;AAAA,EACxC;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,MAAc;AAChB,WAAO,KAAK,oBAAoB,KAAK;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,OAAe;AACjB,WAAO,KAAK,oBAAoB,MAAM;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,YAAoB;AACtB,WAAO,KAAK,oBAAoB,WAAW;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,gBAA0B;AAC5B,WAAO;AAAA,MACL,GAAG,KAAK;AAAA,QAAyB;AAAA,QAAiB,CAAC,UACjD,MAAM,QAAQ,KAAK;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,IAAI,cAAoB;AACtB,UAAM,cAAc,KAAK;AAAA,MACvB;AAAA,MACA,CAAC,UAAU,iBAAiB;AAAA,IAC9B;AAEA,WAAO,IAAI,KAAK,WAAW;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,sBAA+B;AAC7B,WAAO,KAAK,gBAAgB,YAAY;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,sBAA+B;AAC7B,WAAO,KAAK,gBAAgB,YAAY;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,uBAAgC;AAC9B,WAAO,KAAK,gBAAgB,aAAa;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,gBAAgB,OAAwB;AACtC,WAAO,KAAK,OAAO,eAAe,SAAS,KAAK,KAAK;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,cAAiC;AACnC,QAAI,KAAK,aAAa,MAAM;AAC1B,YAAM,IAAI,mBAAmB,2BAA2B,KAAK,IAAI;AAAA,IACnE;AAEA,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,WAAmC;AACjC,UAAM,EAAE,KAAK,aAAa,MAAM,WAAW,cAAc,IAAI,KAAK;AAElE,WAAO,iCAAiC;AAAA,MACtC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAA6B;AACjC,QAAI,KAAK,aAAa,MAAM;AAC1B,YAAM,IAAI,mBAAmB,2BAA2B,KAAK,IAAI;AAAA,IACnE;AAEA,YAAQ,KAAK,SAAS,MAAM;AAAA,MAC1B,KAAK;AACH,eAAO,OAAO,KAAK,KAAK,SAAS,YAAY;AAAA,MAC/C,KAAK;AACH,eAAO,OAAO,KAAK,KAAK,SAAS,YAAY;AAAA,IACjD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,OAAyB;AAC7B,UAAM,MAAM,KAAK,qBAAqB;AAEtC,QAAI;AAEF,aAAO,KAAK,MAAM,GAAG;AAAA,IACvB,SAAS,KAAc;AACrB,YAAM,IAAI,iBAAiB,kCAAkC,KAAK,IAAI;AAAA,IACxE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,MAAuC;AAC3C,WAAO,iCAAiC,KAAK,MAAM;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAwB;AAC5B,WAAO,KAAK,qBAAqB;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,oBAAoB,WAAiD;AACnE,WAAO,KAAK;AAAA,MACV;AAAA,MACA,CAAC,UAAU,OAAO,UAAU;AAAA,IAC9B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,yBACE,WACA,cACG;AACH,QAAI,CAAC,OAAO,OAAO,KAAK,QAAQ,SAAS,GAAG;AAC1C,YAAM,IAAI;AAAA,QACR,WAAW,SAAS;AAAA,QACpB,KAAK;AAAA,MACP;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,OAAO,SAAS;AACnC,QAAI,CAAC,aAAa,KAAK,GAAG;AACxB,YAAM,IAAI;AAAA,QACR,WAAW,SAAS;AAAA,QACpB,KAAK;AAAA,MACP;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,uBAA+B;AAC7B,QAAI,KAAK,UAAU,SAAS,UAAU;AACpC,YAAM,IAAI;AAAA,QACR;AAAA,QACA,KAAK;AAAA,MACP;AAAA,IACF;AAEA,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,CAAC,OAAO,IAAI,4BAA4B,CAAC,EACvC,OACA,SACA,SACA;AACA,UAAM,cAAc,KAAK,UAAU,QAAQ;AAC3C,QAAI,SAAS,GAAG;AACd,aAAO,QAAQ,QAAQ,gBAAgB,WAAW,MAAM,SAAS;AAAA,IACnE;AAEA,UAAM,aAAa,OAAO,OAAO,CAAC,GAAG,SAAS;AAAA,MAC5C,OAAO,QAAQ,UAAU,OAAO,OAAO,QAAQ,QAAQ;AAAA,IACzD,CAAC;AAED,UAAM,EAAE,MAAM,UAAU,IAAI,KAAK;AACjC,UAAM,QAAQ,QAAQ,EAAE,MAAM,UAAU,GAAG,UAAU;AAErD,WAAO,GAAG,QAAQ,QAAQ,eAAe,WAAW,KAAK,SAAS,CAAC,IAAI,KAAK;AAAA,EAC9E;AACF;;;ACvQO,IAAM,iBAAN,MAAqB;AAAA,EAC1B,YAAoB,QAAwB;AAAxB;AAAA,EAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7C,MAAM,YAAY,UAAkB,MAAsC;AACxE,UAAM,MAAM,MAAM,KAAK,MAAM,UAAU,IAAI;AAE3C,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,UAAkB,MAAuC;AACvE,UAAM,MAAM,MAAM,KAAK,MAAM,UAAU,IAAI;AAE3C,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA,EAEA,MAAM,MAAM,UAAkB,MAA2C;AACvE,UAAM,MAAM,MAAM,KAAK,OAAO,eAAe;AAAA,MAC3C,GAAG;AAAA,MACH;AAAA,IACF,CAAC;AAED,WAAO,IAAI,YAAY,GAAG;AAAA,EAC5B;AACF;","names":[]}