Files
private_image/backend/src/utils/fromBase64Url.spec.ts
T

83 lines
3.0 KiB
TypeScript

import { randomBytes } from 'node:crypto';
import { AES_GCM_KEY_BYTE_LENGTH } from '../consts/crypto/aesGcmKeyByteLength';
import { fromBase64Url } from './fromBase64Url';
const toBase64UrlLikeClient = (bytes: Buffer): string => {
let base64 = bytes.toString('base64').split('+').join('-').split('/').join('_');
while (base64.endsWith('=')) {
base64 = base64.slice(0, -1);
}
return base64;
};
describe('fromBase64Url', () => {
it('decodes unpadded base64url utf8 payloads', () => {
expect(fromBase64Url('aGVsbG8')).toEqual(Buffer.from('hello', 'utf8'));
});
it('decodes empty string to an empty buffer', () => {
expect(fromBase64Url('')).toEqual(Buffer.alloc(0));
});
it('decodes base64url with - and _ instead of + and /', () => {
const bytes = Buffer.from([0xfb, 0xff, 0xfe]);
const url = bytes.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
expect(fromBase64Url(url)).toEqual(bytes);
});
it('restores padding when the input omits trailing equals', () => {
expect(fromBase64Url('AQIDBA')).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04]));
});
it('decodes when padding characters are already present', () => {
expect(fromBase64Url('YQ==')).toEqual(Buffer.from('a', 'utf8'));
expect(fromBase64Url('YWI=')).toEqual(Buffer.from('ab', 'utf8'));
});
it('matches Node base64url decoding for random 32-byte keys', () => {
const dek = randomBytes(AES_GCM_KEY_BYTE_LENGTH);
const secret = dek.toString('base64url');
expect(fromBase64Url(secret)).toEqual(dek);
expect(fromBase64Url(secret)).toEqual(Buffer.from(secret, 'base64url'));
});
describe('round-trip with client-style encoding', () => {
it.each([0, 1, 2, 3, 4, 12, 16, 32, 64])('decodes %i-byte payloads', length => {
const bytes = randomBytes(length);
const encoded = toBase64UrlLikeClient(bytes);
expect(fromBase64Url(encoded)).toEqual(bytes);
});
it('decodes fixed-byte patterns used in crypto tests', () => {
const iv = Buffer.alloc(12, 0x11);
const dek = Buffer.alloc(32, 0x42);
expect(fromBase64Url(toBase64UrlLikeClient(iv))).toEqual(iv);
expect(fromBase64Url(toBase64UrlLikeClient(dek))).toEqual(dek);
});
});
describe('agrees with Buffer.from(..., "base64url")', () => {
const samples = [
'aGVsbG8',
toBase64UrlLikeClient(Buffer.from([0xff, 0xfe, 0xfd])),
randomBytes(48).toString('base64url')
];
it.each(samples)('matches for encoded sample', encoded => {
expect(fromBase64Url(encoded)).toEqual(Buffer.from(encoded, 'base64url'));
});
});
it('decodes standard base64 alphabet when plus and slash are passed through', () => {
const standard = Buffer.from('binary+/', 'utf8').toString('base64');
expect(fromBase64Url(standard)).toEqual(Buffer.from('binary+/', 'utf8'));
});
});