@tsndr/cloudflare-worker-jwt
Advanced tools
Comparing version 2.4.4 to 2.4.5
220
index.js
@@ -1,152 +0,102 @@ | ||
"use strict"; | ||
(() => { | ||
// src/utils.ts | ||
function bytesToByteString(bytes) { | ||
let byteStr = ""; | ||
for (let i = 0; i < bytes.byteLength; i++) { | ||
byteStr += String.fromCharCode(bytes[i]); | ||
} | ||
return byteStr; | ||
} | ||
function byteStringToBytes(byteStr) { | ||
let bytes = new Uint8Array(byteStr.length); | ||
for (let i = 0; i < byteStr.length; i++) { | ||
bytes[i] = byteStr.charCodeAt(i); | ||
} | ||
return bytes; | ||
} | ||
function arrayBufferToBase64String(arrayBuffer) { | ||
return btoa(bytesToByteString(new Uint8Array(arrayBuffer))); | ||
} | ||
function base64StringToArrayBuffer(b64str) { | ||
return byteStringToBytes(atob(b64str)).buffer; | ||
} | ||
function textToArrayBuffer(str) { | ||
return byteStringToBytes(decodeURI(encodeURIComponent(str))); | ||
} | ||
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) { | ||
const encoder = new TextEncoder(); | ||
const charCodes = encoder.encode(str); | ||
const binaryStr = String.fromCharCode(...charCodes); | ||
return btoa(binaryStr).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); | ||
} | ||
function pemToBinary(pem) { | ||
return base64StringToArrayBuffer(pem.replace(/-+(BEGIN|END).*/g, "").replace(/\s/g, "")); | ||
} | ||
async function importTextSecret(key, algorithm, keyUsages) { | ||
return await crypto.subtle.importKey("raw", textToArrayBuffer(key), algorithm, true, keyUsages); | ||
} | ||
async function importJwk(key, algorithm, keyUsages) { | ||
return await crypto.subtle.importKey("jwk", key, algorithm, true, keyUsages); | ||
} | ||
async function importPublicKey(key, algorithm, keyUsages) { | ||
return await crypto.subtle.importKey("spki", pemToBinary(key), algorithm, true, keyUsages); | ||
} | ||
async function importPrivateKey(key, algorithm, keyUsages) { | ||
return await crypto.subtle.importKey("pkcs8", pemToBinary(key), algorithm, true, keyUsages); | ||
} | ||
async function importKey(key, algorithm, keyUsages) { | ||
if (typeof key === "object") | ||
return importJwk(key, algorithm, keyUsages); | ||
if (typeof key !== "string") | ||
throw new Error("Unsupported key type!"); | ||
if (key.includes("PUBLIC")) | ||
return importPublicKey(key, algorithm, keyUsages); | ||
if (key.includes("PRIVATE")) | ||
return importPrivateKey(key, algorithm, keyUsages); | ||
return importTextSecret(key, algorithm, keyUsages); | ||
} | ||
function decodePayload(raw) { | ||
try { | ||
const bytes = Array.from(atob(raw), (char) => char.charCodeAt(0)); | ||
const decodedString = new TextDecoder("utf-8").decode(new Uint8Array(bytes)); | ||
return JSON.parse(decodedString); | ||
} catch { | ||
return; | ||
} | ||
} | ||
// src/index.ts | ||
if (typeof crypto === "undefined" || !crypto.subtle) | ||
throw new Error("SubtleCrypto not supported!"); | ||
var algorithms = { | ||
ES256: { name: "ECDSA", namedCurve: "P-256", hash: { name: "SHA-256" } }, | ||
ES384: { name: "ECDSA", namedCurve: "P-384", hash: { name: "SHA-384" } }, | ||
ES512: { name: "ECDSA", namedCurve: "P-521", hash: { name: "SHA-512" } }, | ||
HS256: { name: "HMAC", hash: { name: "SHA-256" } }, | ||
HS384: { name: "HMAC", hash: { name: "SHA-384" } }, | ||
HS512: { name: "HMAC", hash: { name: "SHA-512" } }, | ||
RS256: { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } }, | ||
RS384: { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-384" } }, | ||
RS512: { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-512" } } | ||
}; | ||
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, a JWK object or a CryptoKey object"); | ||
if (typeof options.algorithm !== "string") | ||
throw new Error("options.algorithm must be a string"); | ||
import { textToArrayBuffer, arrayBufferToBase64Url, base64UrlToArrayBuffer, textToBase64Url, importKey, decodePayload } from "./utils"; | ||
if (typeof crypto === 'undefined' || !crypto.subtle) | ||
throw new Error('SubtleCrypto not supported!'); | ||
const algorithms = { | ||
ES256: { name: 'ECDSA', namedCurve: 'P-256', hash: { name: 'SHA-256' } }, | ||
ES384: { name: 'ECDSA', namedCurve: 'P-384', hash: { name: 'SHA-384' } }, | ||
ES512: { name: 'ECDSA', namedCurve: 'P-521', hash: { name: 'SHA-512' } }, | ||
HS256: { name: 'HMAC', hash: { name: 'SHA-256' } }, | ||
HS384: { name: 'HMAC', hash: { name: 'SHA-384' } }, | ||
HS512: { name: 'HMAC', hash: { name: 'SHA-512' } }, | ||
RS256: { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-256' } }, | ||
RS384: { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-384' } }, | ||
RS512: { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-512' } } | ||
}; | ||
/** | ||
* 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 | CryptoKey} 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, a JWK object or a CryptoKey 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"); | ||
throw new Error('algorithm not found'); | ||
if (!payload.iat) | ||
payload.iat = Math.floor(Date.now() / 1e3); | ||
payload.iat = Math.floor(Date.now() / 1000); | ||
const partialToken = `${textToBase64Url(JSON.stringify({ ...options.header, alg: options.algorithm }))}.${textToBase64Url(JSON.stringify(payload))}`; | ||
const key = secret instanceof CryptoKey ? secret : await importKey(secret, algorithm, ["sign"]); | ||
const key = secret instanceof CryptoKey ? secret : await importKey(secret, algorithm, ['sign']); | ||
const signature = await crypto.subtle.sign(algorithm, key, textToArrayBuffer(partialToken)); | ||
return `${partialToken}.${arrayBufferToBase64Url(signature)}`; | ||
} | ||
async function verify(token, secret, options = { algorithm: "HS256", throwError: false }) { | ||
if (typeof options === "string") | ||
options = { algorithm: options, throwError: false }; | ||
options = { algorithm: "HS256", throwError: false, ...options }; | ||
if (typeof token !== "string") | ||
throw new Error("token must be a string"); | ||
if (typeof secret !== "string" && typeof secret !== "object") | ||
throw new Error("secret must be a string, a JWK object or a CryptoKey object"); | ||
if (typeof options.algorithm !== "string") | ||
throw new Error("options.algorithm must be a string"); | ||
const tokenParts = token.split("."); | ||
} | ||
/** | ||
* Verifies the integrity of the token and returns a boolean value. | ||
* | ||
* @param {string} token The token string generated by `jwt.sign()`. | ||
* @param {string | JsonWebKey | CryptoKey} 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 async function verify(token, secret, options = { algorithm: 'HS256', throwError: false }) { | ||
if (typeof options === 'string') | ||
options = { algorithm: options, throwError: false }; | ||
options = { algorithm: 'HS256', throwError: false, ...options }; | ||
if (typeof token !== 'string') | ||
throw new Error('token must be a string'); | ||
if (typeof secret !== 'string' && typeof secret !== 'object') | ||
throw new Error('secret must be a string, a JWK object or a CryptoKey object'); | ||
if (typeof options.algorithm !== 'string') | ||
throw new Error('options.algorithm must be a string'); | ||
const tokenParts = token.split('.'); | ||
if (tokenParts.length !== 3) | ||
throw new Error("token must consist of 3 parts"); | ||
throw new Error('token must consist of 3 parts'); | ||
const algorithm = algorithms[options.algorithm]; | ||
if (!algorithm) | ||
throw new Error("algorithm not found"); | ||
throw new Error('algorithm not found'); | ||
const { payload } = decode(token); | ||
try { | ||
if (!payload) | ||
throw new Error("PARSE_ERROR"); | ||
if (payload.nbf && payload.nbf > Math.floor(Date.now() / 1e3)) | ||
throw new Error("NOT_YET_VALID"); | ||
if (payload.exp && payload.exp <= Math.floor(Date.now() / 1e3)) | ||
throw new Error("EXPIRED"); | ||
const key = secret instanceof CryptoKey ? secret : await importKey(secret, algorithm, ["verify"]); | ||
return await crypto.subtle.verify(algorithm, key, base64UrlToArrayBuffer(tokenParts[2]), textToArrayBuffer(`${tokenParts[0]}.${tokenParts[1]}`)); | ||
} catch (err) { | ||
if (options.throwError) | ||
throw err; | ||
return false; | ||
if (!payload) | ||
throw new Error('PARSE_ERROR'); | ||
if (payload.nbf && payload.nbf > Math.floor(Date.now() / 1000)) | ||
throw new Error('NOT_YET_VALID'); | ||
if (payload.exp && payload.exp <= Math.floor(Date.now() / 1000)) | ||
throw new Error('EXPIRED'); | ||
const key = secret instanceof CryptoKey ? secret : await importKey(secret, algorithm, ['verify']); | ||
return await crypto.subtle.verify(algorithm, key, base64UrlToArrayBuffer(tokenParts[2]), textToArrayBuffer(`${tokenParts[0]}.${tokenParts[1]}`)); | ||
} | ||
} | ||
function decode(token) { | ||
catch (err) { | ||
if (options.throwError) | ||
throw err; | ||
return false; | ||
} | ||
} | ||
/** | ||
* Returns the payload **without** verifying the integrity of the token. Please use `jwt.verify()` first to keep your application secure! | ||
* | ||
* @param {string} token The token string generated by `jwt.sign()`. | ||
* @returns {JwtData} Returns an `object` containing `header` and `payload`. | ||
*/ | ||
export function decode(token) { | ||
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, '/')) | ||
}; | ||
} | ||
var src_default = { | ||
} | ||
export default { | ||
sign, | ||
verify, | ||
decode | ||
}; | ||
})(); | ||
}; |
{ | ||
"name": "@tsndr/cloudflare-worker-jwt", | ||
"version": "2.4.4", | ||
"version": "2.4.5", | ||
"description": "A lightweight JWT implementation with ZERO dependencies for Cloudflare Worker", | ||
@@ -12,3 +12,3 @@ "type": "module", | ||
"scripts": { | ||
"build": "esbuild src/index.ts --bundle --outfile=index.js && tsc --emitDeclarationOnly", | ||
"build": "tsc", | ||
"test": "jest" | ||
@@ -34,12 +34,11 @@ }, | ||
"devDependencies": { | ||
"@cloudflare/workers-types": "^4.20240129.0", | ||
"@cloudflare/workers-types": "^4.20231025.0", | ||
"@jest/globals": "^29.7.0", | ||
"@types/jest": "^29.5.11", | ||
"@types/node": "^20.11.14", | ||
"esbuild": "^0.20.0", | ||
"@types/jest": "^29.5.8", | ||
"@types/node": "^20.9.0", | ||
"jest": "^29.7.0", | ||
"ts-jest": "^29.1.2", | ||
"ts-node": "^10.9.2", | ||
"typescript": "^5.3.3" | ||
"ts-jest": "^29.1.1", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^5.2.2" | ||
} | ||
} |
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
19998
8
7
317