Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 13 additions & 25 deletions src/format.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ describe('format(credentials)', function () {
describe('arguments', function () {
describe('credentials', function () {
it('should be required', function () {
assert.throws(
() => (format as any)(),
/argument credentials is required/,
);
assert.throws(() => (format as any)(), /Expected an object/);
});

it('should accept credentials', function () {
Expand All @@ -17,65 +14,56 @@ describe('format(credentials)', function () {
});

it('should reject null', function () {
assert.throws(
format.bind(null, null as any),
/argument credentials is required/,
);
assert.throws(format.bind(null, null as any), /Expected an object/);
});

it('should reject a number', function () {
assert.throws(
format.bind(null, 42 as any),
/argument credentials is required/,
);
assert.throws(format.bind(null, 42 as any), /Expected an object/);
});

it('should reject a string', function () {
assert.throws(
format.bind(null, '' as any),
/argument credentials is required/,
);
assert.throws(format.bind(null, '' as any), /Expected an object/);
});

it('should reject an object without name', function () {
assert.throws(
format.bind(null, { pass: 'bar' } as any),
/argument credentials is required to have name and pass properties/,
/Object must have string properties "name" and "pass"/,
);
});

it('should reject an object without pass', function () {
assert.throws(
format.bind(null, { name: 'foo' } as any),
/argument credentials is required to have name and pass properties/,
/Object must have string properties "name" and "pass"/,
);
});

it('should reject an object with non-string name', function () {
assert.throws(
format.bind(null, { name: 42, pass: 'bar' } as any),
/argument credentials is required to have name and pass properties/,
/Object must have string properties "name" and "pass"/,
);
});

it('should reject an object with non-string pass', function () {
assert.throws(
format.bind(null, { name: 'foo', pass: 42 } as any),
/argument credentials is required to have name and pass properties/,
/Object must have string properties "name" and "pass"/,
);
});

it('should reject userid containing colon', function () {
assert.throws(
format.bind(null, { name: 'foo:bar', pass: 'baz' }),
/must not contain a colon or control characters/,
/must not contain a colon/,
);
});

it('should reject control chars in userid', function () {
assert.throws(
format.bind(null, { name: 'foo\u0000bar', pass: 'baz' }),
/must not contain a colon or control characters/,
/must not contain control characters/,
);
});

Expand All @@ -96,21 +84,21 @@ describe('format(credentials)', function () {
});

describe('with empty password', function () {
it('should throw', function () {
it('should return header', function () {
const header = format({ name: 'foo', pass: '' });
assert.strictEqual(header, 'Basic Zm9vOg==');
});
});

describe('with empty userid', function () {
it('should throw', function () {
it('should return header', function () {
const header = format({ name: '', pass: 'pass' });
assert.strictEqual(header, 'Basic OnBhc3M=');
});
});

describe('with empty userid and pass', function () {
it('should throw', function () {
it('should return header', function () {
const header = format({ name: '', pass: '' });
assert.strictEqual(header, 'Basic Og==');
});
Expand Down
32 changes: 12 additions & 20 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface Credentials {

export function parse(string: string): Credentials | undefined {
if (typeof string !== 'string') {
return undefined;
throw new TypeError('Expected a string');
}

// parse header
Expand All @@ -52,39 +52,31 @@ export function parse(string: string): Credentials | undefined {
* @public
*/
export function format(credentials: Credentials): string {
if (!credentials) {
throw new TypeError('argument credentials is required');
}

if (typeof credentials !== 'object') {
throw new TypeError('argument credentials is required to be an object');
if (typeof credentials !== 'object' || credentials === null) {
throw new TypeError('Expected an object');
}

if (
typeof credentials.name !== 'string' ||
typeof credentials.pass !== 'string'
) {
throw new TypeError(
'argument credentials is required to have name and pass properties',
);
throw new TypeError('Object must have string properties "name" and "pass"');
}

if (
credentials.name.includes(':') || // RFC 7617 disallows colon in username
CONTROL_CHARS_REGEXP.test(credentials.name)
) {
throw new TypeError(
'argument credentials.name must not contain a colon or control characters',
);
// RFC 7617 disallows colon in username
if (credentials.name.includes(':')) {
throw new TypeError('Object "name" must not contain a colon');
}

if (CONTROL_CHARS_REGEXP.test(credentials.pass)) {
const str = credentials.name + ':' + credentials.pass;

if (CONTROL_CHARS_REGEXP.test(str)) {
throw new TypeError(
'argument credentials.pass must not contain control characters',
'Object "name" and "pass" must not contain control characters',
);
}

return 'Basic ' + base64.encode(credentials.name + ':' + credentials.pass);
return 'Basic ' + base64.encode(str);
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/parse.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { describe, it, assert } from 'vitest';
import { parse } from './index.js';

describe('parse(string)', function () {
describe('with undefined string', function () {
it('should return undefined', function () {
assert.strictEqual((parse as any)(), undefined);
describe('with non string', function () {
it('should throw', function () {
assert.throws(() => parse(undefined as any), /Expected a string/);
});
});

Expand Down
Loading