Skip to content

Commit

Permalink
feat: add bufferFromBase64
Browse files Browse the repository at this point in the history
  • Loading branch information
frytg committed Dec 17, 2024
1 parent da48d91 commit 3aea9d8
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 55 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This repository is work in progress.
## Tools

- [`@frytg/check-required-env`](./check-required-env/README.md) - Check a required environment variable
- [`@frytg/crypto`](./crypto/README.md) - Crypto utilities (hash, hmac, etc.)
- [`@frytg/dates`](./dates/README.md) - Date utilities around Luxon
- [`@frytg/logger`](./logger/README.md) - Pre-configuredWinston logging wrapper

Expand Down
4 changes: 4 additions & 0 deletions crypto/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Crypto Changelog

## 2024-12-17 - 0.1.0

- feat: add `bufferFromBase64`

## 2024-12-16 - 0.0.3

- fix: optimize JSDoc for Deno
Expand Down
2 changes: 1 addition & 1 deletion crypto/deno.jsonc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://jsr.io/schema/config-file.v1.json",
"name": "@frytg/crypto",
"version": "0.0.3",
"version": "0.1.0",
"exports": {
"./hash": "./hash.ts",
"./hmac": "./hmac.ts"
Expand Down
2 changes: 2 additions & 0 deletions crypto/hash.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// load packages
import { test } from '@cross/test'
import { assertEquals } from '@std/assert'

// load module
import { hashSha256, hashSha512 } from './hash.ts'

test('hashSha256 - generates correct SHA-256 hashes', () => {
Expand Down
1 change: 1 addition & 0 deletions crypto/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* ```
*/

// load packages
import { createHash } from 'node:crypto'

const HEX_ENCODING = 'hex'
Expand Down
118 changes: 118 additions & 0 deletions crypto/hmac-buffer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// load packages
import { Buffer } from 'node:buffer'
import { test } from '@cross/test'
import { assertEquals, assertThrows } from '@std/assert'

// load module
import { bufferFromBase64, bufferFromHex } from './hmac.ts'

test('bufferFromBase64 - converts base64 strings to Buffer correctly', () => {
const testCases = [
{
input: 'aGVsbG8=', // "hello"
expected: Buffer.from('hello'),
},
{
input: '', // empty string
expected: Buffer.from(''),
},
{
input: 'YWJjZGVmMTIzNDU2', // "abcdef123456"
expected: Buffer.from('abcdef123456'),
},
{
input: 'Zm9vIGJhcg==', // "foo bar"
expected: Buffer.from('foo bar'),
},
]

for (const { input, expected } of testCases) {
assertEquals(bufferFromBase64(input), expected, `bufferFromBase64("${input}") should return correct Buffer`)
}
})

test('bufferFromBase64 - validates base64 strings correctly', () => {
// Valid base64 strings should work
const validBase64 = [
'aGVsbG8=', // normal case
'', // empty string
'YQ==', // single char padding
'YWI=', // double char padding
'YWJj', // no padding needed
'YWJjZA==', // standard padding
]

for (const base64 of validBase64) {
assertEquals(
typeof bufferFromBase64(base64),
'object',
`bufferFromBase64 should accept valid base64 string "${base64}"`,
)
}

// Invalid base64 strings should throw
const invalidBase64 = [
'!@#$', // invalid characters
'hello', // not base64
'YW JjZA==', // spaces
]

for (const base64 of invalidBase64) {
assertThrows(
() => bufferFromBase64(base64),
Error,
'base64', // error message should include
`bufferFromBase64 should reject invalid base64 string "${base64}"`,
)
}
})

test('bufferFromHex - converts hex strings to Buffer correctly', () => {
const testCases = [
{
input: '0123456789abcdef',
expected: Buffer.from([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]),
},
{
input: 'ff00ff00',
expected: Buffer.from([0xff, 0x00, 0xff, 0x00]),
},
{
input: '',
expected: Buffer.from([]),
},
]

for (const { input, expected } of testCases) {
assertEquals(bufferFromHex(input), expected, `bufferFromHex("${input}") should return correct Buffer`)
}
})

test('bufferFromHex - validates hex strings correctly', () => {
// Valid hex strings should work
const validHexes = ['0123456789abcdef', 'ABCDEF', '', '00', 'ff', 'deadbeef']

for (const hex of validHexes) {
assertEquals(typeof bufferFromHex(hex), 'object', `bufferFromHex should accept valid hex string "${hex}"`)
}

// Invalid hex strings should throw
const invalidHexes = [
'0123456789abcdefg', // invalid hex char
'0123456789abcdef0', // odd length
'xyz', // non-hex chars
'gh', // non-hex chars
' ', // whitespace
'12 34', // spaces
'12-34', // dashes
]

for (const hex of invalidHexes) {
assertThrows(
() => bufferFromHex(hex),
Error,
'Invalid hex string', // error message should include
`bufferFromHex should reject invalid hex string "${hex}"`,
)
}
})
57 changes: 4 additions & 53 deletions crypto/hmac.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Buffer } from 'node:buffer'
// load packages
import { test } from '@cross/test'
import { assertEquals, assertThrows } from '@std/assert'
import { assertEquals } from '@std/assert'

import { bufferFromHex, hmacSha256, hmacSha512 } from './hmac.ts'
// load module
import { hmacSha256, hmacSha512 } from './hmac.ts'

test('hmacSha256 - generates correct HMAC SHA-256 hashes', () => {
const testCases = [
Expand Down Expand Up @@ -54,53 +55,3 @@ test('hmacSha512 - generates correct HMAC SHA-512 hashes', () => {
assertEquals(hmacSha512(input, key), expected, `hmacSha512("${input}", "${key}") should return "${expected}"`)
}
})

test('bufferFromHex - converts hex strings to Buffer correctly', () => {
const testCases = [
{
input: '0123456789abcdef',
expected: Buffer.from([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]),
},
{
input: 'ff00ff00',
expected: Buffer.from([0xff, 0x00, 0xff, 0x00]),
},
{
input: '',
expected: Buffer.from([]),
},
]

for (const { input, expected } of testCases) {
assertEquals(bufferFromHex(input), expected, `bufferFromHex("${input}") should return correct Buffer`)
}
})

test('bufferFromHex - validates hex strings correctly', () => {
// Valid hex strings should work
const validHexes = ['0123456789abcdef', 'ABCDEF', '', '00', 'ff', 'deadbeef']

for (const hex of validHexes) {
assertEquals(typeof bufferFromHex(hex), 'object', `bufferFromHex should accept valid hex string "${hex}"`)
}

// Invalid hex strings should throw
const invalidHexes = [
'0123456789abcdefg', // invalid hex char
'0123456789abcdef0', // odd length
'xyz', // non-hex chars
'gh', // non-hex chars
' ', // whitespace
'12 34', // spaces
'12-34', // dashes
]

for (const hex of invalidHexes) {
assertThrows(
() => bufferFromHex(hex),
Error,
'Invalid hex string',
`bufferFromHex should reject invalid hex string "${hex}"`,
)
}
})
23 changes: 22 additions & 1 deletion crypto/hmac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
* ```
*/

// load package
// load packages
import { Buffer } from 'node:buffer'
import { createHmac } from 'node:crypto'

const BASE64_ENCODING = 'base64'
const BASE64_REGEX = /^[0-9a-zA-Z+/=]*$/
const HEX_ENCODING = 'hex'
const HEX_REGEX = /^[0-9a-fA-F]*$/

Expand Down Expand Up @@ -53,6 +55,25 @@ export const hmacSha512 = (str: string | Buffer, key: string | Buffer): string =
return createHmac('sha512', keyBuffer).update(str).digest(HEX_ENCODING)
}

/**
* Converts a base64 string to a Buffer for use with HMAC
* @param base64 - The base64 string to convert
* @returns Buffer
*
* @example
* ```ts
* import { hmacSha512, bufferFromBase64 } from '@frytg/crypto/hmac'
*
* hmacSha512('hello world', bufferFromBase64('MDEyMzQ1Njc4OWFiY2RlZg=='))
* ```
*/
export const bufferFromBase64 = (base64: string): Buffer => {
// check if base64 string is valid
if (!BASE64_REGEX.test(base64)) throw new Error('Invalid base64 string')

return Buffer.from(base64, BASE64_ENCODING)
}

/**
* Converts a hexadecimal string to a Buffer for use with HMAC
* @param hex - The hexadecimal string to convert
Expand Down

0 comments on commit 3aea9d8

Please sign in to comment.