diff --git a/packages/commons/package.json b/packages/commons/package.json index aac76fecea..430e78c51b 100644 --- a/packages/commons/package.json +++ b/packages/commons/package.json @@ -40,6 +40,10 @@ "default": "./lib/esm/index.js" } }, + "./utils/base64": { + "import": "./lib/esm/fromBase64.js", + "require": "./lib/cjs/fromBase64.js" + }, "./types": { "import": "./lib/esm/types/index.js", "require": "./lib/cjs/types/index.js" @@ -47,6 +51,10 @@ }, "typesVersions": { "*": { + "utils/base64": [ + "lib/cjs/fromBase64.d.ts", + "lib/esm/fromBase64.d.ts" + ], "types": [ "lib/cjs/types/index.d.ts", "lib/esm/types/index.d.ts" @@ -75,4 +83,4 @@ "devDependencies": { "@aws-lambda-powertools/testing-utils": "file:../testing" } -} +} \ No newline at end of file diff --git a/packages/commons/src/fromBase64.ts b/packages/commons/src/fromBase64.ts new file mode 100644 index 0000000000..5fd0236c64 --- /dev/null +++ b/packages/commons/src/fromBase64.ts @@ -0,0 +1,35 @@ +const BASE64_REGEX = /^[A-Za-z0-9+/]*={0,2}$/; + +/** + * Convert a base64 string to a Uint8Array. + * + * The input string must be a valid base64 string, otherwise an error will be thrown. + * + * The encoding parameter is optional and defaults to 'utf-8'. + * + * @example + * ```ts + * import { fromBase64 } from '@aws-lambda-powertools/commons/utils/base64'; + * + * const encodedValue = 'aGVsbG8gd29ybGQ='; + * + * const decoded = fromBase64(encodedValue); + * // new Uint8Array([ 97, 71, 86, 115, 98, 71, 56, 103, 100, 50, 57, 121, 98, 71, 81, 61 ]); + * ``` + * + * @param input The base64 string to convert to a Uint8Array + * @param encoding The encoding of the input string (optional) + */ +const fromBase64 = (input: string, encoding?: BufferEncoding): Uint8Array => { + if ((input.length * 3) % 4 !== 0) { + throw new TypeError(`Incorrect padding on base64 string.`); + } + if (!BASE64_REGEX.exec(input)) { + throw new TypeError(`Invalid base64 string.`); + } + const buffer = encoding ? Buffer.from(input, encoding) : Buffer.from(input); + + return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); +}; + +export { fromBase64 }; diff --git a/packages/commons/tests/unit/fromBase64.test.ts b/packages/commons/tests/unit/fromBase64.test.ts new file mode 100644 index 0000000000..23ee84ecd3 --- /dev/null +++ b/packages/commons/tests/unit/fromBase64.test.ts @@ -0,0 +1,66 @@ +/** + * Test fromBase64 function + * + * @group unit/commons/fromBase64 + */ +import { fromBase64 } from '../../src/fromBase64.js'; + +describe('Function: fromBase64', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + }); + + it('returns the Uint8Array from a base64 string', () => { + // Prepare + const base64 = 'aGVsbG8gd29ybGQ='; + const expected = new Uint8Array([ + 97, 71, 86, 115, 98, 71, 56, 103, 100, 50, 57, 121, 98, 71, 81, 61, + ]); + + // Act + const result = fromBase64(base64); + + // Assess + expect(result).toStrictEqual(expected); + }); + + it('throws a TypeError when the base64 string has incorrect padding', () => { + // Prepare + const base64 = 'aGVsbG8gd29ybGQ'; + + // Act + const result = (): Uint8Array => fromBase64(base64); + + // Assess + expect(result).toThrow(TypeError); + expect(result).toThrow(`Incorrect padding on base64 string.`); + }); + + it('throws a TypeError when the base64 string is invalid', () => { + // Prepare + const base64 = 'a-VsbG8gd29ybGQ='; + + // Act + const result = (): Uint8Array => fromBase64(base64); + + // Assess + expect(result).toThrow(TypeError); + expect(result).toThrow(`Invalid base64 string.`); + }); + + it('uses the provided encoding to create the Uint8Array', () => { + // Prepare + const base64 = 'aGVsbG8gd29ybGQ='; + const encoding = 'utf8'; + const expected = new Uint8Array([ + 97, 71, 86, 115, 98, 71, 56, 103, 100, 50, 57, 121, 98, 71, 81, 61, + ]); + + // Act + const result = fromBase64(base64, encoding); + + // Assess + expect(result).toStrictEqual(expected); + }); +});