Files
TeleUploader/test/s3-auth-edge.test.ts
T

88 lines
3.2 KiB
TypeScript
Raw Normal View History

import { describe, expect, it } from 'bun:test';
import { buildCanonicalQueryString, isS3Request, verifyBodyHash } from '../src/interfaces/s3/auth';
/**
* Edge-case coverage for the exported pure helpers in the SigV4 auth module
* that the existing s3-auth.test.ts does not exercise directly.
*/
describe('verifyBodyHash (body-integrity check)', () => {
it('returns null when no x-amz-content-sha256 header is present (unsigned allowed)', () => {
expect(verifyBodyHash('abc123', {})).toBeNull();
});
it('returns null for UNSIGNED-PAYLOAD', () => {
expect(verifyBodyHash('anything', { 'x-amz-content-sha256': 'UNSIGNED-PAYLOAD' })).toBeNull();
});
it('returns null for STREAMING-* payloads (checked after signature)', () => {
expect(
verifyBodyHash('anything', { 'x-amz-content-sha256': 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD' }),
).toBeNull();
});
it('returns BadDigest when the claimed and actual hash differ', () => {
const result = verifyBodyHash('actual-hash', { 'x-amz-content-sha256': 'claimed-hash' });
expect(result).not.toBeNull();
expect(result?.isValid).toBe(false);
expect(result?.errorCode).toBe('BadDigest');
});
it('returns null when the claimed and actual hash match', () => {
expect(verifyBodyHash('same', { 'x-amz-content-sha256': 'same' })).toBeNull();
});
});
describe('isS3Request (Auth header detection)', () => {
it('detects AWS4-HMAC-SHA256 authorization', () => {
expect(isS3Request({ authorization: 'AWS4-HMAC-SHA256 Credential=...' })).toBe(true);
});
it('returns false for bearer/simple auth', () => {
expect(isS3Request({ authorization: 'Bearer token' })).toBe(false);
});
it('returns false when no authorization header', () => {
expect(isS3Request({})).toBe(false);
});
it('is case-sensitive on the AWS4-HMAC-SHA256 scheme prefix', () => {
expect(isS3Request({ authorization: 'aws4-hmac-sha256 Credential=...' })).toBe(false);
});
});
describe('buildCanonicalQueryString (SigV4 query canonicalization)', () => {
it('returns empty string for no params', () => {
expect(buildCanonicalQueryString(new URLSearchParams())).toBe('');
});
it('sorts params by encoded key then value (byte order)', () => {
const sp = new URLSearchParams('b=2&a=1&c=3');
expect(buildCanonicalQueryString(sp)).toBe('a=1&b=2&c=3');
});
it('sorts by encoded (key=value) pair, not raw key', () => {
const sp = new URLSearchParams({ 'list-type': '2', prefix: 'x' });
// 'list-type' (l...) sorts before 'prefix' (p...)
expect(buildCanonicalQueryString(sp)).toBe('list-type=2&prefix=x');
});
it('excludes the X-Amz-Signature key when requested', () => {
const sp = new URLSearchParams({
'X-Amz-Signature': 'sig',
'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
});
const result = buildCanonicalQueryString(sp, new Set(['X-Amz-Signature']));
expect(result).not.toContain('X-Amz-Signature');
expect(result).toContain('X-Amz-Algorithm');
});
it('percent-encodes special characters', () => {
const sp = new URLSearchParams();
sp.set('key with space', 'a&b');
const result = buildCanonicalQueryString(sp);
// space → %20, & → %26
expect(result).toContain('%20');
expect(result).toContain('%26');
});
});