http-auth-utils
Advanced tools
Comparing version 3.0.5 to 4.0.0
@@ -0,1 +1,12 @@ | ||
# [4.0.0](https://github.com/nfroidure/http-auth-utils/compare/v3.0.5...v4.0.0) (2023-03-09) | ||
### Bug Fixes | ||
* **src/utils.ts:** handle parsing values with equal signs ([e640f21](https://github.com/nfroidure/http-auth-utils/commit/e640f21e9cd4115831eb968aabbe4c384c0f03ce)), closes [#17](https://github.com/nfroidure/http-auth-utils/issues/17) | ||
* **src/utils.ts:** normalize keys to lowercase ([bfb33f4](https://github.com/nfroidure/http-auth-utils/commit/bfb33f4c22f6596258343505ecdd5205d9a75309)) | ||
* **src/utils.ts:** normalize stale value to lowercase ([8d10f8c](https://github.com/nfroidure/http-auth-utils/commit/8d10f8c0da0599eb48df746ebfadb9092760c93b)) | ||
## [3.0.5](https://github.com/nfroidure/http-auth-utils/compare/v3.0.4...v3.0.5) (2023-01-05) | ||
@@ -2,0 +13,0 @@ |
import BASIC from './mechanisms/basic.js'; | ||
import DIGEST from './mechanisms/digest.js'; | ||
import BEARER from './mechanisms/bearer.js'; | ||
export declare type Mechanism = { | ||
export type Mechanism = { | ||
type: string; | ||
@@ -6,0 +6,0 @@ parseWWWAuthenticateRest(rest: string): Record<string, string>; |
/** | ||
* @module http-auth-utils/mechanisms/basic | ||
*/ | ||
declare type BasicWWWAuthenticateData = { | ||
type BasicWWWAuthenticateData = { | ||
realm: string; | ||
}; | ||
declare type BasicAuthorizationData = { | ||
type BasicAuthorizationData = { | ||
username: string; | ||
@@ -9,0 +9,0 @@ password: string; |
@@ -5,9 +5,9 @@ /** | ||
declare const AUTHORIZED_ERROR_CODES: readonly ["invalid_request", "invalid_token", "insufficient_scope"]; | ||
declare type BearerWWWAuthenticateData = { | ||
type BearerWWWAuthenticateData = { | ||
realm: string; | ||
scope?: string; | ||
error?: typeof AUTHORIZED_ERROR_CODES[number]; | ||
error?: (typeof AUTHORIZED_ERROR_CODES)[number]; | ||
error_description?: string; | ||
}; | ||
declare type BearerAuthorizationData = { | ||
type BearerAuthorizationData = { | ||
hash: string; | ||
@@ -14,0 +14,0 @@ }; |
/** | ||
* @module http-auth-utils/mechanisms/digest | ||
*/ | ||
declare type DigestWWWAuthenticateData = { | ||
type DigestWWWAuthenticateData = { | ||
realm: string; | ||
@@ -13,3 +13,3 @@ domain?: string; | ||
}; | ||
declare type DigestAuthorizationData = { | ||
type DigestAuthorizationData = { | ||
username: string; | ||
@@ -61,3 +61,3 @@ realm: string; | ||
* Build the WWW Authenticate header rest. | ||
* @param {Object} data The content from wich to build the rest. | ||
* @param {Object} data The content from which to build the rest. | ||
* @return {String} The built rest. | ||
@@ -113,3 +113,3 @@ * @example | ||
* Build the Authorization header rest. | ||
* @param {Object} data The content from wich to build the rest. | ||
* @param {Object} data The content from which to build the rest. | ||
* @return {String} The rest built. | ||
@@ -116,0 +116,0 @@ * @example |
@@ -15,2 +15,3 @@ /** | ||
]; | ||
const CASE_INSENSITIVE_WWW_AUTHENTICATE_VALUES = ['stale']; | ||
const REQUIRED_AUTHORIZATION_KEYS = [ | ||
@@ -69,7 +70,7 @@ 'username', | ||
parseWWWAuthenticateRest: function parseWWWAuthenticateRest(rest) { | ||
return parseHTTPHeadersQuotedKeyValueSet(rest, AUTHORIZED_WWW_AUTHENTICATE_KEYS, REQUIRED_WWW_AUTHENTICATE_KEYS); | ||
return parseHTTPHeadersQuotedKeyValueSet(rest, AUTHORIZED_WWW_AUTHENTICATE_KEYS, REQUIRED_WWW_AUTHENTICATE_KEYS, CASE_INSENSITIVE_WWW_AUTHENTICATE_VALUES); | ||
}, | ||
/** | ||
* Build the WWW Authenticate header rest. | ||
* @param {Object} data The content from wich to build the rest. | ||
* @param {Object} data The content from which to build the rest. | ||
* @return {String} The built rest. | ||
@@ -129,3 +130,3 @@ * @example | ||
* Build the Authorization header rest. | ||
* @param {Object} data The content from wich to build the rest. | ||
* @param {Object} data The content from which to build the rest. | ||
* @return {String} The rest built. | ||
@@ -132,0 +133,0 @@ * @example |
@@ -1,2 +0,2 @@ | ||
export declare function parseHTTPHeadersQuotedKeyValueSet(contents: string, authorizedKeys: string[], requiredKeys?: string[]): Record<string, string>; | ||
export declare function parseHTTPHeadersQuotedKeyValueSet(contents: string, authorizedKeys: string[], requiredKeys?: string[], valuesToNormalize?: string[]): Record<string, string>; | ||
export declare function buildHTTPHeadersQuotedKeyValueSet(data: Record<string, string>, authorizedKeys: string[], requiredKeys?: string[]): string; |
@@ -17,3 +17,3 @@ import { YError } from 'yerror'; | ||
// FIXME: Create a real parser | ||
export function parseHTTPHeadersQuotedKeyValueSet(contents, authorizedKeys, requiredKeys = []) { | ||
export function parseHTTPHeadersQuotedKeyValueSet(contents, authorizedKeys, requiredKeys = [], valuesToNormalize = []) { | ||
const matches = contents.trim().match(KEYVALUE_REGEXP); | ||
@@ -24,11 +24,13 @@ if (!matches) | ||
.map((part, partPosition) => { | ||
const pair = part.split(EQUAL); | ||
if (2 !== pair.length) { | ||
throw new YError('E_MALFORMED_QUOTEDKEYVALUE', partPosition, part, pair.length); | ||
const [key, ...rest] = part.split(EQUAL); | ||
const value = rest.join(EQUAL); | ||
if (0 === rest.length) { | ||
throw new YError('E_MALFORMED_QUOTEDKEYVALUE', partPosition, part); | ||
} | ||
return pair; | ||
return [key, value]; | ||
}) | ||
.reduce(function (parsedValues, [name, value], valuePosition) { | ||
if (-1 === authorizedKeys.indexOf(name)) { | ||
throw new YError('E_UNAUTHORIZED_KEY', valuePosition, name); | ||
const normalizedName = name.toLowerCase(); | ||
if (-1 === authorizedKeys.indexOf(normalizedName)) { | ||
throw new YError('E_UNAUTHORIZED_KEY', valuePosition, normalizedName); | ||
} | ||
@@ -46,3 +48,6 @@ /* | ||
*/ | ||
parsedValues[name] = value.replace(/^"(.+(?="$))"$/, '$1'); | ||
const strippedValue = value.replace(/^"(.+(?="$))"$/, '$1'); | ||
parsedValues[normalizedName] = valuesToNormalize.includes(normalizedName) | ||
? strippedValue.toLowerCase() | ||
: strippedValue; | ||
return parsedValues; | ||
@@ -49,0 +54,0 @@ }, {}); |
@@ -18,2 +18,13 @@ import { describe, test } from '@jest/globals'; | ||
}); | ||
test('should work with equals in key values', () => { | ||
neatequal(parseHTTPHeadersQuotedKeyValueSet('realm="testrealm@host.com", ' + | ||
'qop="auth, auth-int", ' + | ||
'nonce="dGVzdCBzdHJpbmc=", ' + | ||
'opaque="5ccc069c403ebaf9f0171e9517f40e41"', ['realm', 'qop', 'nonce', 'opaque'], ['realm', 'qop', 'nonce', 'opaque']), { | ||
realm: 'testrealm@host.com', | ||
qop: 'auth, auth-int', | ||
nonce: 'dGVzdCBzdHJpbmc=', | ||
opaque: '5ccc069c403ebaf9f0171e9517f40e41', | ||
}); | ||
}); | ||
test('should work with parse-able non-quoted data', () => { | ||
@@ -30,2 +41,28 @@ neatequal(parseHTTPHeadersQuotedKeyValueSet('realm="testrealm@host.com", ' + | ||
}); | ||
test('should normalize all keys to lowercase', () => { | ||
neatequal(parseHTTPHeadersQuotedKeyValueSet('Realm="testrealm@host.com", ' + | ||
'qop="auth, auth-int", ' + | ||
'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", ' + | ||
'opaque="5ccc069c403ebaf9f0171e9517f40e41", ' + | ||
'Stale="false"', ['realm', 'qop', 'nonce', 'opaque', 'stale'], ['realm', 'qop', 'nonce', 'opaque', 'stale']), { | ||
realm: 'testrealm@host.com', | ||
qop: 'auth, auth-int', | ||
nonce: 'dcd98b7102dd2f0e8b11d0f600bfb0c093', | ||
opaque: '5ccc069c403ebaf9f0171e9517f40e41', | ||
stale: 'false', | ||
}); | ||
}); | ||
test('should normalize values to lowercase for given keys', () => { | ||
neatequal(parseHTTPHeadersQuotedKeyValueSet('realm="testrealm-UPPERCASE@host.com", ' + | ||
'qop="auth, auth-int", ' + | ||
'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", ' + | ||
'opaque="5ccc069c403ebaf9f0171e9517f40e41", ' + | ||
'stale=TRUE', ['realm', 'qop', 'nonce', 'opaque', 'stale'], ['realm', 'qop', 'nonce', 'opaque', 'stale'], ['stale']), { | ||
realm: 'testrealm-UPPERCASE@host.com', | ||
qop: 'auth, auth-int', | ||
nonce: 'dcd98b7102dd2f0e8b11d0f600bfb0c093', | ||
opaque: '5ccc069c403ebaf9f0171e9517f40e41', | ||
stale: 'true', | ||
}); | ||
}); | ||
test('should fail with bad quoted value pair', () => { | ||
@@ -32,0 +69,0 @@ assert.throws(() => parseHTTPHeadersQuotedKeyValueSet('realm', []), /E_MALFORMED_QUOTEDKEYVALUE/); |
{ | ||
"name": "http-auth-utils", | ||
"version": "3.0.5", | ||
"description": "Parse, build and deal with HTTP authorization headers.", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"metapak": { | ||
@@ -32,5 +27,10 @@ "data": { | ||
}, | ||
"name": "http-auth-utils", | ||
"version": "4.0.0", | ||
"description": "Parse, build and deal with HTTP authorization headers.", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"scripts": { | ||
"architecture": "jsarch 'src/**/*.ts' > ARCHITECTURE.md && git add ARCHITECTURE.md", | ||
"build": "rimraf -f 'dist' && tsc --outDir dist", | ||
"build": "rimraf 'dist' && tsc --outDir dist", | ||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md", | ||
@@ -76,22 +76,23 @@ "cli": "env NODE_ENV=${NODE_ENV:-cli}", | ||
"devDependencies": { | ||
"@typescript-eslint/eslint-plugin": "^5.36.0", | ||
"@typescript-eslint/parser": "^5.36.0", | ||
"commitizen": "^4.2.5", | ||
"@types/node": "^18.14.6", | ||
"@typescript-eslint/eslint-plugin": "^5.54.1", | ||
"@typescript-eslint/parser": "^5.54.1", | ||
"commitizen": "^4.3.0", | ||
"conventional-changelog-cli": "^2.2.2", | ||
"coveralls": "^3.1.1", | ||
"cz-conventional-changelog": "^3.3.0", | ||
"esbuild": "^0.15.6", | ||
"esbuild": "^0.17.11", | ||
"esbuild-jest": "^0.5.0", | ||
"eslint": "^8.23.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint": "^8.35.0", | ||
"eslint-config-prettier": "^8.7.0", | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"jest": "^29.0.1", | ||
"jest": "^29.5.0", | ||
"jsarch": "^6.0.0", | ||
"jsdoc-to-markdown": "^7.1.1", | ||
"jsdoc-to-markdown": "^8.0.0", | ||
"metapak": "^4.0.6", | ||
"metapak-nfroidure": "13.0.0", | ||
"metapak-nfroidure": "13.1.1", | ||
"neatequal": "^1.0.0", | ||
"prettier": "^2.7.1", | ||
"rimraf": "^3.0.2", | ||
"typescript": "^4.8.2" | ||
"prettier": "^2.8.4", | ||
"rimraf": "^4.4.0", | ||
"typescript": "^4.9.5" | ||
}, | ||
@@ -133,2 +134,7 @@ "dependencies": { | ||
"url": "https://github.com/jakepruitt" | ||
}, | ||
{ | ||
"name": "Corentin Girard", | ||
"email": "corentin.girard@datadoghq.com", | ||
"url": "https://github.com/Drarig29" | ||
} | ||
@@ -135,0 +141,0 @@ ], |
@@ -541,3 +541,3 @@ [//]: # ( ) | ||
| --- | --- | --- | | ||
| data | <code>Object</code> | The content from wich to build the rest. | | ||
| data | <code>Object</code> | The content from which to build the rest. | | ||
@@ -609,3 +609,3 @@ **Example** | ||
| --- | --- | --- | | ||
| data | <code>Object</code> | The content from wich to build the rest. | | ||
| data | <code>Object</code> | The content from which to build the rest. | | ||
@@ -672,4 +672,5 @@ **Example** | ||
- [Jake Pruitt](https://github.com/jakepruitt) | ||
- [Corentin Girard](https://github.com/Drarig29) | ||
# License | ||
[MIT](https://github.com/nfroidure/http-auth-utils/blob/master/LICENSE) |
@@ -27,3 +27,3 @@ /** | ||
scope?: string; | ||
error?: typeof AUTHORIZED_ERROR_CODES[number]; | ||
error?: (typeof AUTHORIZED_ERROR_CODES)[number]; | ||
error_description?: string; | ||
@@ -35,3 +35,3 @@ }; | ||
type BearerAuthorizedErrorCodes = typeof AUTHORIZED_ERROR_CODES[number]; | ||
type BearerAuthorizedErrorCodes = (typeof AUTHORIZED_ERROR_CODES)[number]; | ||
@@ -38,0 +38,0 @@ /* Architecture Note #1.1: Bearer mechanism |
@@ -21,2 +21,3 @@ /** | ||
]; | ||
const CASE_INSENSITIVE_WWW_AUTHENTICATE_VALUES = ['stale']; | ||
type DigestWWWAuthenticateData = { | ||
@@ -105,2 +106,3 @@ realm: string; | ||
REQUIRED_WWW_AUTHENTICATE_KEYS, | ||
CASE_INSENSITIVE_WWW_AUTHENTICATE_VALUES, | ||
) as DigestWWWAuthenticateData; | ||
@@ -111,3 +113,3 @@ }, | ||
* Build the WWW Authenticate header rest. | ||
* @param {Object} data The content from wich to build the rest. | ||
* @param {Object} data The content from which to build the rest. | ||
* @return {String} The built rest. | ||
@@ -181,3 +183,3 @@ * @example | ||
* Build the Authorization header rest. | ||
* @param {Object} data The content from wich to build the rest. | ||
* @param {Object} data The content from which to build the rest. | ||
* @return {String} The rest built. | ||
@@ -184,0 +186,0 @@ * @example |
@@ -30,2 +30,21 @@ import { describe, test } from '@jest/globals'; | ||
test('should work with equals in key values', () => { | ||
neatequal( | ||
parseHTTPHeadersQuotedKeyValueSet( | ||
'realm="testrealm@host.com", ' + | ||
'qop="auth, auth-int", ' + | ||
'nonce="dGVzdCBzdHJpbmc=", ' + | ||
'opaque="5ccc069c403ebaf9f0171e9517f40e41"', | ||
['realm', 'qop', 'nonce', 'opaque'], | ||
['realm', 'qop', 'nonce', 'opaque'], | ||
), | ||
{ | ||
realm: 'testrealm@host.com', | ||
qop: 'auth, auth-int', | ||
nonce: 'dGVzdCBzdHJpbmc=', | ||
opaque: '5ccc069c403ebaf9f0171e9517f40e41', | ||
}, | ||
); | ||
}); | ||
test('should work with parse-able non-quoted data', () => { | ||
@@ -50,2 +69,45 @@ neatequal( | ||
test('should normalize all keys to lowercase', () => { | ||
neatequal( | ||
parseHTTPHeadersQuotedKeyValueSet( | ||
'Realm="testrealm@host.com", ' + | ||
'qop="auth, auth-int", ' + | ||
'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", ' + | ||
'opaque="5ccc069c403ebaf9f0171e9517f40e41", ' + | ||
'Stale="false"', | ||
['realm', 'qop', 'nonce', 'opaque', 'stale'], | ||
['realm', 'qop', 'nonce', 'opaque', 'stale'], | ||
), | ||
{ | ||
realm: 'testrealm@host.com', | ||
qop: 'auth, auth-int', | ||
nonce: 'dcd98b7102dd2f0e8b11d0f600bfb0c093', | ||
opaque: '5ccc069c403ebaf9f0171e9517f40e41', | ||
stale: 'false', | ||
}, | ||
); | ||
}); | ||
test('should normalize values to lowercase for given keys', () => { | ||
neatequal( | ||
parseHTTPHeadersQuotedKeyValueSet( | ||
'realm="testrealm-UPPERCASE@host.com", ' + | ||
'qop="auth, auth-int", ' + | ||
'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", ' + | ||
'opaque="5ccc069c403ebaf9f0171e9517f40e41", ' + | ||
'stale=TRUE', | ||
['realm', 'qop', 'nonce', 'opaque', 'stale'], | ||
['realm', 'qop', 'nonce', 'opaque', 'stale'], | ||
['stale'], | ||
), | ||
{ | ||
realm: 'testrealm-UPPERCASE@host.com', // should not be changed | ||
qop: 'auth, auth-int', | ||
nonce: 'dcd98b7102dd2f0e8b11d0f600bfb0c093', | ||
opaque: '5ccc069c403ebaf9f0171e9517f40e41', | ||
stale: 'true', | ||
}, | ||
); | ||
}); | ||
test('should fail with bad quoted value pair', () => { | ||
@@ -52,0 +114,0 @@ assert.throws( |
@@ -24,2 +24,3 @@ import { YError } from 'yerror'; | ||
requiredKeys: string[] = [], | ||
valuesToNormalize: string[] = [], | ||
): Record<string, string> { | ||
@@ -31,18 +32,15 @@ const matches = contents.trim().match(KEYVALUE_REGEXP); | ||
.map((part, partPosition) => { | ||
const pair = part.split(EQUAL); | ||
if (2 !== pair.length) { | ||
throw new YError( | ||
'E_MALFORMED_QUOTEDKEYVALUE', | ||
partPosition, | ||
part, | ||
pair.length, | ||
); | ||
const [key, ...rest] = part.split(EQUAL); | ||
const value = rest.join(EQUAL); | ||
if (0 === rest.length) { | ||
throw new YError('E_MALFORMED_QUOTEDKEYVALUE', partPosition, part); | ||
} | ||
return pair; | ||
return [key, value]; | ||
}) | ||
.reduce(function (parsedValues, [name, value], valuePosition) { | ||
if (-1 === authorizedKeys.indexOf(name)) { | ||
throw new YError('E_UNAUTHORIZED_KEY', valuePosition, name); | ||
const normalizedName = name.toLowerCase(); | ||
if (-1 === authorizedKeys.indexOf(normalizedName)) { | ||
throw new YError('E_UNAUTHORIZED_KEY', valuePosition, normalizedName); | ||
} | ||
/* | ||
@@ -59,3 +57,8 @@ * Regular expression for stripping paired starting and ending double quotes off the value: | ||
*/ | ||
parsedValues[name] = value.replace(/^"(.+(?="$))"$/, '$1'); | ||
const strippedValue = value.replace(/^"(.+(?="$))"$/, '$1'); | ||
parsedValues[normalizedName] = valuesToNormalize.includes(normalizedName) | ||
? strippedValue.toLowerCase() | ||
: strippedValue; | ||
return parsedValues; | ||
@@ -62,0 +65,0 @@ }, {}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
181622
3380
674
21