pkce-challenge
Advanced tools
Comparing version 2.0.0 to 2.1.0
# Changelog | ||
## [2.1.0] - 2019-12-20 | ||
### Added | ||
- `verifyChallenge` exported from index | ||
### Changed | ||
- code/comment formatting | ||
- refactored `random` function | ||
## [2.0.0] - 2019-10-18 | ||
### Added | ||
- CHANGELOG | ||
@@ -11,2 +24,3 @@ - typescript definition | ||
### Removed | ||
- `generateVerifier` export from index | ||
@@ -18,6 +32,6 @@ - `generateChallenge` export from index | ||
### Contributors | ||
- [lordnox] | ||
[lordnox]: https://github.com/lordnox | ||
[2.0.0]: https://github.com/crouchcd/pkce-challenge/releases/tag/2.0.0 | ||
[2.0.0]: https://github.com/crouchcd/pkce-challenge/releases/tag/2.0.0 |
@@ -13,2 +13,6 @@ // Type definitions for pkce-challenge 2.0 | ||
}; | ||
export function verifyChallenge( | ||
code_verifier: string, | ||
expectedChallenge: string | ||
): boolean; | ||
} |
132
index.js
@@ -1,79 +0,87 @@ | ||
const { createHash, randomBytes } = require('crypto'); | ||
const { createHash, randomBytes } = require("crypto"); | ||
/** | ||
* generate cryptographically secure random string | ||
* @param {number} size how long the result should be | ||
* @param {string} mask string to select chars from | ||
/** Generate cryptographically secure random string | ||
* @param {number} size The desired length of the string | ||
* @param {string} mask A mask of characters (no more than 256) to choose from | ||
* @returns {string} The random string | ||
*/ | ||
function random(size, mask) { | ||
let result = ''; | ||
const byteLength = Math.pow(2, 8); | ||
if (mask.length > byteLength) { | ||
const howManySkipped = mask.length - byteLength; | ||
console.warn( | ||
`Mask is longer than 2^8. Last ${howManySkipped} items will be skipped.` | ||
); | ||
} | ||
const subscrValues = randomBytes(size); | ||
const maxMaskLength = Math.min(mask.length, byteLength); | ||
// subscript values range from 0 to 0xFF = 256 | ||
// mask subscripts range from 0 to (maxMaskLength-1) = maxMaskLength | ||
const scalingValue = byteLength / maxMaskLength; | ||
let result = ""; | ||
const randomIndices = randomBytes(size); | ||
const byteLength = Math.pow(2, 8); // 256 | ||
const maskLength = Math.min(mask.length, byteLength); | ||
// the scaling factor breaks down the possible values of bytes (0x00-0xFF) | ||
// into the range of mask indices | ||
const scalingFactor = byteLength / maskLength; | ||
for (let i = 0; i < size; i++) { | ||
const randomIndex = Math.floor(randomIndices[i] / scalingFactor); | ||
result += mask[randomIndex]; | ||
} | ||
return result; | ||
} | ||
for (let i = 0; i < size; i++) { | ||
const randIdx = Math.floor(subscrValues[i] / scalingValue); | ||
result += mask[randIdx]; | ||
} | ||
return result; | ||
} | ||
/** | ||
* @param {string} base64 base64 encoded string to url encode | ||
* @returns {string} base64 url encoded string | ||
/** Base64 url encode a string | ||
* @param {string} base64 The base64 string to url encode | ||
* @returns {string} The base64 url encoded string | ||
*/ | ||
function base64UrlEncode(base64) { | ||
return base64 | ||
.replace(/=/g, '') | ||
.replace(/\+/g, '-') | ||
.replace(/\//g, '_'); | ||
return base64 | ||
.replace(/=/g, "") | ||
.replace(/\+/g, "-") | ||
.replace(/\//g, "_"); | ||
} | ||
/** | ||
* generate a pkce challenge verifier | ||
* @param {number} length length of the verifier | ||
* @returns {string} random string `length` characters long | ||
/** Generate a PKCE challenge verifier | ||
* @param {number} length Length of the verifier | ||
* @returns {string} A random verifier `length` characters long | ||
*/ | ||
function generateVerifier(length) { | ||
const mask = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~'; | ||
return random(length, mask); | ||
const mask = | ||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"; | ||
return random(length, mask); | ||
} | ||
/** | ||
* generate a pkce challenge code from a verifier | ||
* @param {string} code_verifier base64 url encoded string | ||
/** Generate a PKCE challenge code from a verifier | ||
* @param {string} code_verifier | ||
* @returns {string} The base64 url encoded code challenge | ||
*/ | ||
function generateChallenge(code_verifier) { | ||
const hash = createHash('sha256') | ||
.update(code_verifier) | ||
.digest('base64'); | ||
return base64UrlEncode(hash); | ||
const hash = createHash("sha256") | ||
.update(code_verifier) | ||
.digest("base64"); | ||
return base64UrlEncode(hash); | ||
} | ||
/** | ||
* generate a pkce challenge pair | ||
* @param {number} [length=43] specify the length of | ||
* the verifier between 43 and 123 characters long | ||
* @returns {{code_challenge:string, | ||
* code_verifier:string}} pkce challenge pair | ||
/** Generate a PKCE challenge pair | ||
* @param {number} [length=43] Length of the verifer (between 43-128) | ||
* @returns {{code_challenge:string,code_verifier:string}} PKCE challenge pair | ||
*/ | ||
function pkceChallenge(length = 43) { | ||
if (length < 43 || length > 128) { | ||
throw `Expected a length between 43 and 128. Received ${length}.`; | ||
} | ||
function pkceChallenge(length) { | ||
if (!length) length = 43; | ||
const verifier = generateVerifier(length); | ||
const challenge = generateChallenge(verifier); | ||
if (length < 43 || length > 128) { | ||
throw `Expected a length between 43 and 128. Received ${length}.`; | ||
} | ||
return { | ||
code_challenge: challenge, | ||
code_verifier: verifier | ||
}; | ||
const verifier = generateVerifier(length); | ||
const challenge = generateChallenge(verifier); | ||
return { | ||
code_challenge: challenge, | ||
code_verifier: verifier | ||
}; | ||
} | ||
module.exports = pkceChallenge; | ||
module.exports = pkceChallenge; | ||
/** Verify that a code_verifier produces the expected code challenge | ||
* @param {string} code_verifier | ||
* @param {string} expectedChallenge The code challenge to verify | ||
* @returns {boolean} True if challenges are equal. False otherwise. | ||
*/ | ||
function verifyChallenge(code_verifier, expectedChallenge) { | ||
const actualChallenge = generateChallenge(code_verifier); | ||
return actualChallenge === expectedChallenge; | ||
} | ||
module.exports.verifyChallenge = verifyChallenge; |
{ | ||
"name": "pkce-challenge", | ||
"version": "2.0.0", | ||
"description": "Generate a Proof Key for Code Exchange (PKCE) challenge pair", | ||
"version": "2.1.0", | ||
"description": "Generate or verify a Proof Key for Code Exchange (PKCE) challenge pair", | ||
"main": "index.js", | ||
@@ -6,0 +6,0 @@ "types": "./index.d.ts", |
# pkce-challenge | ||
Generate a Proof Key for Code Exchange (PKCE) challenge pair. | ||
Generate or verify a Proof Key for Code Exchange (PKCE) challenge pair. | ||
Read more about [PKCE](https://www.oauth.com/oauth2-servers/pkce/authorization-request/). | ||
## Installation | ||
``` | ||
@@ -11,4 +13,6 @@ npm install pkce-challenge | ||
## Usage | ||
## Usage | ||
Default length for the verifier is 43 | ||
``` | ||
@@ -18,7 +22,9 @@ const pkceChallenge = require('pkce-challenge'); | ||
``` | ||
gives something like: | ||
``` | ||
{ | ||
{ | ||
code_verifier: 'u1ta-MQ0e7TcpHjgz33M2DcBnOQu~aMGxuiZt0QMD1C', | ||
code_challenge: 'CUZX5qE8Wvye6kS_SasIsa8MMxacJftmWdsIA_iKp3I' | ||
code_challenge: 'CUZX5qE8Wvye6kS_SasIsa8MMxacJftmWdsIA_iKp3I' | ||
} | ||
@@ -28,5 +34,18 @@ ``` | ||
### Specify a verifier length | ||
``` | ||
const challenge = pkceChallenge(128); | ||
expect(challenge.code_verifier.length).equals(128); | ||
``` | ||
``` | ||
### Challenge verification | ||
``` | ||
const {verifyChallenge} = require('pkce-challenge'); | ||
expect( | ||
verifyChallenge( | ||
challenge.code_verifier, | ||
challenge.code_challenge | ||
) | ||
).toBe(true); | ||
``` |
@@ -1,34 +0,63 @@ | ||
const { test } = require('tap'); | ||
const pkceChallenge = require('../index'); | ||
const { test } = require("tap"); | ||
const pkceChallenge = require("../index"); | ||
const { verifyChallenge } = pkceChallenge; | ||
test('default verifier length is 43', t => { | ||
t.is(pkceChallenge().code_verifier.length, 43); | ||
t.end(); | ||
test("default verifier length is 43", t => { | ||
t.is(pkceChallenge().code_verifier.length, 43); | ||
t.end(); | ||
}); | ||
test('code_verifier pattern matches', t => { | ||
const pattern = /^[A-Za-z\d\-._~]{43,128}$/ | ||
const challengePair = pkceChallenge(128); | ||
t.match(challengePair.code_verifier, pattern); | ||
t.end(); | ||
test("code_verifier pattern matches", t => { | ||
const pattern = /^[A-Za-z\d\-._~]{43,128}$/; | ||
const challengePair = pkceChallenge(128); | ||
t.match(challengePair.code_verifier, pattern); | ||
t.end(); | ||
}); | ||
test("code_challenge pattern doesn't have [=+/]", t => { | ||
const challengePair = pkceChallenge(128); | ||
const challengePair = pkceChallenge(128); | ||
t.doesNotHave(challengePair.code_challenge, '='); | ||
t.doesNotHave(challengePair.code_challenge, '+'); | ||
t.doesNotHave(challengePair.code_challenge, '/'); | ||
t.end(); | ||
t.doesNotHave(challengePair.code_challenge, "="); | ||
t.doesNotHave(challengePair.code_challenge, "+"); | ||
t.doesNotHave(challengePair.code_challenge, "/"); | ||
t.end(); | ||
}); | ||
test('verifier length < 43 throws error', t => { | ||
t.throws(() => { | ||
pkceChallenge(42); | ||
}, 'Expected a length between 43 and 128. Received 42.'); | ||
t.end(); | ||
test("verifier length < 43 throws error", t => { | ||
t.throws(() => { | ||
pkceChallenge(42); | ||
}, "Expected a length between 43 and 128. Received 42."); | ||
t.end(); | ||
}); | ||
test('verifier length > 128 throws error', t => { | ||
t.throws(() => { | ||
pkceChallenge(129); | ||
}, 'Expected a length between 43 and 128. Received 129.'); | ||
t.end(); | ||
}); | ||
test("verifier length > 128 throws error", t => { | ||
t.throws(() => { | ||
pkceChallenge(129); | ||
}, "Expected a length between 43 and 128. Received 129."); | ||
t.end(); | ||
}); | ||
test("verifyChallenge should return true", t => { | ||
const challengePair = pkceChallenge(); | ||
t.is( | ||
verifyChallenge( | ||
challengePair.code_verifier, | ||
challengePair.code_challenge | ||
), | ||
true | ||
); | ||
t.end(); | ||
}); | ||
test("verifyChallenge should return false", t => { | ||
const challengePair = pkceChallenge(); | ||
t.is( | ||
verifyChallenge( | ||
challengePair.code_verifier, | ||
challengePair.code_challenge + "a" | ||
), | ||
false | ||
); | ||
t.end(); | ||
}); |
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
7911
145
49