@tsndr/cloudflare-worker-jwt
Advanced tools
Comparing version 2.2.7 to 2.2.8
@@ -95,2 +95,12 @@ /// <reference types="@cloudflare/workers-types" /> | ||
/** | ||
* Verifies the integrity of the token and returns a boolean value. | ||
* | ||
* @param {string} token The token string generated by `jwt.sign()`. | ||
* @param {string | JsonWebKey} secret The string which was used to sign the payload. | ||
* @param {JWTVerifyOptions | JWTAlgorithm} options The options object or the algorithm. | ||
* @throws {Error | string} Throws an error `string` if the token is invalid or an `Error-Object` if there's a validation issue. | ||
* @returns {Promise<boolean>} Returns `true` if signature, `nbf` (if set) and `exp` (if set) are valid, otherwise returns `false`. | ||
*/ | ||
export declare function verify(token: string, secret: string | JsonWebKey, options?: JwtVerifyOptions | JwtAlgorithm): Promise<boolean>; | ||
/** | ||
* Signs a payload and returns the token | ||
@@ -106,12 +116,2 @@ * | ||
/** | ||
* Verifies the integrity of the token and returns a boolean value. | ||
* | ||
* @param {string} token The token string generated by `jwt.sign()`. | ||
* @param {string | JsonWebKey} secret The string which was used to sign the payload. | ||
* @param {JWTVerifyOptions | JWTAlgorithm} options The options object or the algorithm. | ||
* @throws {Error | string} Throws an error `string` if the token is invalid or an `Error-Object` if there's a validation issue. | ||
* @returns {Promise<boolean>} Returns `true` if signature, `nbf` (if set) and `exp` (if set) are valid, otherwise returns `false`. | ||
*/ | ||
export declare function verify(token: string, secret: string | JsonWebKey, options?: JwtVerifyOptions | JwtAlgorithm): Promise<boolean>; | ||
/** | ||
* Returns the payload **without** verifying the integrity of the token. Please use `jwt.verify()` first to keep your application secure! | ||
@@ -118,0 +118,0 @@ * |
186
index.js
if (typeof crypto === 'undefined' || !crypto.subtle) | ||
throw new Error('SubtleCrypto not supported!'); | ||
function base64UrlParse(s) { | ||
// @ts-ignore | ||
return new Uint8Array(Array.prototype.map.call(atob(s.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, '')), c => c.charCodeAt(0))); | ||
// return new Uint8Array(Array.from(atob(s.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, ''))).map(c => c.charCodeAt(0))) | ||
} | ||
function base64UrlStringify(a) { | ||
// @ts-ignore | ||
return btoa(String.fromCharCode.apply(0, a)).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); | ||
// return btoa(String.fromCharCode.apply(0, Array.from(a))).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_') | ||
} | ||
const algorithms = { | ||
@@ -24,18 +14,79 @@ ES256: { name: 'ECDSA', namedCurve: 'P-256', hash: { name: 'SHA-256' } }, | ||
}; | ||
function _utf8ToUint8Array(str) { | ||
return base64UrlParse(btoa(unescape(encodeURIComponent(str)))); | ||
function bytesToByteString(bytes) { | ||
let byteStr = ''; | ||
for (let i = 0; i < bytes.byteLength; i++) { | ||
byteStr += String.fromCharCode(bytes[i]); | ||
} | ||
return byteStr; | ||
} | ||
function _str2ab(str) { | ||
str = atob(str); | ||
const buf = new ArrayBuffer(str.length); | ||
const bufView = new Uint8Array(buf); | ||
for (let i = 0, strLen = str.length; i < strLen; i++) { | ||
bufView[i] = str.charCodeAt(i); | ||
function byteStringToBytes(byteStr) { | ||
let bytes = new Uint8Array(byteStr.length); | ||
for (let i = 0; i < byteStr.length; i++) { | ||
bytes[i] = byteStr.charCodeAt(i); | ||
} | ||
return buf; | ||
return bytes; | ||
} | ||
function _decodePayload(raw) { | ||
function arrayBufferToBase64String(arrayBuffer) { | ||
const byteArray = new Uint8Array(arrayBuffer); | ||
const byteStr = bytesToByteString(byteArray); | ||
return btoa(byteStr); | ||
} | ||
function base64StringToArrayBuffer(b64str) { | ||
const byteStr = atob(b64str); | ||
const bytes = byteStringToBytes(byteStr); | ||
return bytes.buffer; | ||
} | ||
function textToArrayBuffer(str) { | ||
const buf = decodeURI(encodeURIComponent(str)); // 2 bytes for each char | ||
const bytes = byteStringToBytes(buf); | ||
return bytes; | ||
} | ||
// @ts-ignore | ||
function arrayBufferToText(arrayBuffer) { | ||
const byteArray = new Uint8Array(arrayBuffer); | ||
const byteStr = bytesToByteString(byteArray); | ||
return byteStr; | ||
} | ||
function arrayBufferToBase64Url(arrayBuffer) { | ||
return arrayBufferToBase64String(arrayBuffer).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); | ||
} | ||
function base64UrlToArrayBuffer(b64url) { | ||
return base64StringToArrayBuffer(b64url.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, '')); | ||
} | ||
function textToBase64Url(str) { | ||
return btoa(str).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); | ||
} | ||
function pemToBinary(pem) { | ||
return base64StringToArrayBuffer(pem.replace(/-+(BEGIN|END).*/g, '').replace(/\s/g, '')); | ||
} | ||
async function importTextSecret(key, algorithm) { | ||
return await crypto.subtle.importKey("raw", textToArrayBuffer(key), algorithm, true, ["verify", "sign"]); | ||
} | ||
async function importJwk(key, algorithm) { | ||
return await crypto.subtle.importKey("jwk", key, algorithm, true, ["verify", "sign"]); | ||
} | ||
async function importPublicKey(key, algorithm) { | ||
return await crypto.subtle.importKey("spki", pemToBinary(key), algorithm, true, ["verify"]); | ||
} | ||
async function importPrivateKey(key, algorithm) { | ||
return await crypto.subtle.importKey("pkcs8", pemToBinary(key), algorithm, true, ["sign"]); | ||
} | ||
async function importKey(key, algorithm) { | ||
if (typeof key === 'object') | ||
return importJwk(key, algorithm); | ||
if (typeof key !== 'string') | ||
throw new Error('Unsupported key type!'); | ||
if (key.includes('PUBLIC')) | ||
return importPublicKey(key, algorithm); | ||
if (key.includes('PRIVATE')) | ||
return importPrivateKey(key, algorithm); | ||
return importTextSecret(key, algorithm); | ||
} | ||
function decodePayload(raw) { | ||
switch (raw.length % 4) { | ||
case 0: | ||
break; | ||
case 1: | ||
raw += '==='; | ||
break; | ||
case 2: | ||
@@ -48,6 +99,6 @@ raw += '=='; | ||
default: | ||
throw new Error('Illegal base64url string!'); | ||
throw new Error('Invalid base64url string!'); | ||
} | ||
try { | ||
return JSON.parse(decodeURIComponent(escape(atob(raw)))); | ||
return JSON.parse(atob(raw)); | ||
} | ||
@@ -59,44 +110,2 @@ catch { | ||
/** | ||
* Signs a payload and returns the token | ||
* | ||
* @param {JwtPayload} payload The payload object. To use `nbf` (Not Before) and/or `exp` (Expiration Time) add `nbf` and/or `exp` to the payload. | ||
* @param {string | JsonWebKey} secret A string which is used to sign the payload. | ||
* @param {JwtSignOptions | JwtAlgorithm | string} [options={ algorithm: 'HS256', header: { typ: 'JWT' } }] The options object or the algorithm. | ||
* @throws {Error} If there's a validation issue. | ||
* @returns {Promise<string>} Returns token as a `string`. | ||
*/ | ||
export async function sign(payload, secret, options = { algorithm: 'HS256', header: { typ: 'JWT' } }) { | ||
if (typeof options === 'string') | ||
options = { algorithm: options, header: { typ: 'JWT' } }; | ||
options = { algorithm: 'HS256', header: { typ: 'JWT' }, ...options }; | ||
if (payload === null || typeof payload !== 'object') | ||
throw new Error('payload must be an object'); | ||
if (typeof secret !== 'string' && typeof secret !== 'object') | ||
throw new Error('secret must be a string or a JWK object'); | ||
if (typeof options.algorithm !== 'string') | ||
throw new Error('options.algorithm must be a string'); | ||
const algorithm = algorithms[options.algorithm]; | ||
if (!algorithm) | ||
throw new Error('algorithm not found'); | ||
if (!payload.iat) | ||
payload.iat = Math.floor(Date.now() / 1000); | ||
const payloadAsJSON = JSON.stringify(payload); | ||
const partialToken = `${base64UrlStringify(_utf8ToUint8Array(JSON.stringify({ ...options.header, alg: options.algorithm })))}.${base64UrlStringify(_utf8ToUint8Array(payloadAsJSON))}`; | ||
let keyFormat = 'raw'; | ||
let keyData; | ||
if (typeof secret === 'object') { | ||
keyFormat = 'jwk'; | ||
keyData = secret; | ||
} | ||
else if (typeof secret === 'string' && secret.startsWith('-----BEGIN')) { | ||
keyFormat = 'pkcs8'; | ||
keyData = _str2ab(secret.replace(/-----BEGIN.*?-----/g, '').replace(/-----END.*?-----/g, '').replace(/\s/g, '')); | ||
} | ||
else | ||
keyData = _utf8ToUint8Array(secret); | ||
const key = await crypto.subtle.importKey(keyFormat, keyData, algorithm, false, ['sign']); | ||
const signature = await crypto.subtle.sign(algorithm, key, _utf8ToUint8Array(partialToken)); | ||
return `${partialToken}.${base64UrlStringify(new Uint8Array(signature))}`; | ||
} | ||
/** | ||
* Verifies the integrity of the token and returns a boolean value. | ||
@@ -142,18 +151,35 @@ * | ||
} | ||
let keyFormat = 'raw'; | ||
let keyData; | ||
if (typeof secret === 'object') { | ||
keyFormat = 'jwk'; | ||
keyData = secret; | ||
} | ||
else if (typeof secret === 'string' && secret.startsWith('-----BEGIN')) { | ||
keyFormat = 'spki'; | ||
keyData = _str2ab(secret.replace(/-----BEGIN.*?-----/g, '').replace(/-----END.*?-----/g, '').replace(/\s/g, '')); | ||
} | ||
else | ||
keyData = _utf8ToUint8Array(secret); | ||
const key = await crypto.subtle.importKey(keyFormat, keyData, algorithm, false, ['verify']); | ||
return await crypto.subtle.verify(algorithm, key, base64UrlParse(tokenParts[2]), _utf8ToUint8Array(`${tokenParts[0]}.${tokenParts[1]}`)); | ||
const key = await importKey(secret, algorithm); | ||
return await crypto.subtle.verify(algorithm, key, base64UrlToArrayBuffer(tokenParts[2]), textToArrayBuffer(`${tokenParts[0]}.${tokenParts[1]}`)); | ||
} | ||
/** | ||
* Signs a payload and returns the token | ||
* | ||
* @param {JwtPayload} payload The payload object. To use `nbf` (Not Before) and/or `exp` (Expiration Time) add `nbf` and/or `exp` to the payload. | ||
* @param {string | JsonWebKey} secret A string which is used to sign the payload. | ||
* @param {JwtSignOptions | JwtAlgorithm | string} [options={ algorithm: 'HS256', header: { typ: 'JWT' } }] The options object or the algorithm. | ||
* @throws {Error} If there's a validation issue. | ||
* @returns {Promise<string>} Returns token as a `string`. | ||
*/ | ||
export async function sign(payload, secret, options = 'HS256') { | ||
if (typeof options === 'string') | ||
options = { algorithm: options }; | ||
options = { algorithm: 'HS256', header: { typ: 'JWT' }, ...options }; | ||
if (!payload || typeof payload !== 'object') | ||
throw new Error('payload must be an object'); | ||
if (!secret || (typeof secret !== 'string' && typeof secret !== 'object')) | ||
throw new Error('secret must be a string or a JWK object'); | ||
if (typeof options.algorithm !== 'string') | ||
throw new Error('options.algorithm must be a string'); | ||
const algorithm = algorithms[options.algorithm]; | ||
if (!algorithm) | ||
throw new Error('algorithm not found'); | ||
if (!payload.iat) | ||
payload.iat = Math.floor(Date.now() / 1000); | ||
const partialToken = `${textToBase64Url(JSON.stringify({ ...options.header, alg: options.algorithm }))}.${textToBase64Url(JSON.stringify(payload))}`; | ||
const key = await importKey(secret, algorithm); | ||
const signature = await crypto.subtle.sign(algorithm, key, textToArrayBuffer(partialToken)); | ||
return `${partialToken}.${arrayBufferToBase64Url(signature)}`; | ||
} | ||
/** | ||
* Returns the payload **without** verifying the integrity of the token. Please use `jwt.verify()` first to keep your application secure! | ||
@@ -166,4 +192,4 @@ * | ||
return { | ||
header: _decodePayload(token.split('.')[0].replace(/-/g, '+').replace(/_/g, '/')), | ||
payload: _decodePayload(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')) | ||
header: decodePayload(token.split('.')[0].replace(/-/g, '+').replace(/_/g, '/')), | ||
payload: decodePayload(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')) | ||
}; | ||
@@ -170,0 +196,0 @@ } |
{ | ||
"name": "@tsndr/cloudflare-worker-jwt", | ||
"version": "2.2.7", | ||
"version": "2.2.8", | ||
"description": "A lightweight JWT implementation with ZERO dependencies for Cloudflare Worker", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
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
18176
322