1
0

Compare commits

..

9 Commits

Author SHA1 Message Date
91f30929da 2.4.6 2024-02-21 20:07:05 +01:00
93082884fa add esbuild 2024-02-21 19:59:14 +01:00
a9e83968b9 switch to vitest 2024-02-21 19:54:08 +01:00
4c480d5ac7 2.4.5 2024-02-01 01:10:51 +01:00
adf522c3ab Revert "add esbuild for bundling, update dependencies"
This reverts commit 4ceac0270f.
2024-02-01 01:10:32 +01:00
7c82dff259 2.4.4 2024-02-01 00:56:22 +01:00
4ceac0270f add esbuild for bundling, update dependencies 2024-02-01 00:56:12 +01:00
32e00ac6b9 2.4.3 2024-01-26 15:03:51 +01:00
Nick DeGroot
247da9b396 🐛 Fix verification relying on a signing key 2024-01-26 15:02:52 +01:00
11 changed files with 1124 additions and 3319 deletions

2
.gitignore vendored
View File

@@ -146,3 +146,5 @@ dist
# Custom # Custom
/index.js /index.js
/index.d.ts /index.d.ts
/utils.js
/utils.d.ts

View File

@@ -3,7 +3,9 @@
.gitignore .gitignore
.nvmrc .nvmrc
coverage/ coverage/
jest.config.ts vite.config.ts
src/ src/
tests/ tests/
tsconfig.json tsconfig.json
utils.*
*.tgz

View File

@@ -1,9 +0,0 @@
import type { Config } from 'jest'
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
verbose: true
}
export default config

4354
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "@tsndr/cloudflare-worker-jwt", "name": "@tsndr/cloudflare-worker-jwt",
"version": "2.4.2", "version": "2.4.6",
"description": "A lightweight JWT implementation with ZERO dependencies for Cloudflare Worker", "description": "A lightweight JWT implementation with ZERO dependencies for Cloudflare Worker",
"type": "module", "type": "module",
"exports": "./index.js", "exports": "./index.js",
@@ -9,8 +9,8 @@
"node": ">=18" "node": ">=18"
}, },
"scripts": { "scripts": {
"build": "tsc", "build": "tsc & esbuild --bundle --target=esnext --platform=neutral --outfile=index.js src/index.ts & wait",
"test": "jest" "test": "vitest"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -30,13 +30,11 @@
}, },
"homepage": "https://github.com/tsndr/cloudflare-worker-jwt#readme", "homepage": "https://github.com/tsndr/cloudflare-worker-jwt#readme",
"devDependencies": { "devDependencies": {
"@cloudflare/workers-types": "^4.20231025.0", "@cloudflare/workers-types": "^4.20240208.0",
"@jest/globals": "^29.7.0", "@edge-runtime/vm": "^3.2.0",
"@types/jest": "^29.5.8", "@types/node": "^20.11.19",
"@types/node": "^20.9.0", "ts-node": "^10.9.2",
"jest": "^29.7.0", "typescript": "^5.3.3",
"ts-jest": "^29.1.1", "vitest": "^1.3.1"
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
} }
} }

View File

@@ -156,7 +156,7 @@ export async function sign<Payload = {}, Header = {}>(payload: JwtPayload<Payloa
const partialToken = `${textToBase64Url(JSON.stringify({ ...options.header, alg: options.algorithm }))}.${textToBase64Url(JSON.stringify(payload))}` const partialToken = `${textToBase64Url(JSON.stringify({ ...options.header, alg: options.algorithm }))}.${textToBase64Url(JSON.stringify(payload))}`
const key = secret instanceof CryptoKey ? secret : await importKey(secret, algorithm) const key = secret instanceof CryptoKey ? secret : await importKey(secret, algorithm, ['sign'])
const signature = await crypto.subtle.sign(algorithm, key, textToArrayBuffer(partialToken)) const signature = await crypto.subtle.sign(algorithm, key, textToArrayBuffer(partialToken))
return `${partialToken}.${arrayBufferToBase64Url(signature)}` return `${partialToken}.${arrayBufferToBase64Url(signature)}`
@@ -208,7 +208,7 @@ export async function verify(token: string, secret: string | JsonWebKey | Crypto
if (payload.exp && payload.exp <= Math.floor(Date.now() / 1000)) if (payload.exp && payload.exp <= Math.floor(Date.now() / 1000))
throw new Error('EXPIRED') throw new Error('EXPIRED')
const key = secret instanceof CryptoKey ? secret : await importKey(secret, algorithm) 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]}`)) return await crypto.subtle.verify(algorithm, key, base64UrlToArrayBuffer(tokenParts[2]), textToArrayBuffer(`${tokenParts[0]}.${tokenParts[1]}`))
} catch(err) { } catch(err) {

View File

@@ -49,36 +49,37 @@ export function pemToBinary(pem: string): ArrayBuffer {
return base64StringToArrayBuffer(pem.replace(/-+(BEGIN|END).*/g, '').replace(/\s/g, '')) return base64StringToArrayBuffer(pem.replace(/-+(BEGIN|END).*/g, '').replace(/\s/g, ''))
} }
export async function importTextSecret(key: string, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> { type KeyUsages = 'sign' | 'verify';
return await crypto.subtle.importKey("raw", textToArrayBuffer(key), algorithm, true, ["verify", "sign"]) export async function importTextSecret(key: string, algorithm: SubtleCryptoImportKeyAlgorithm, keyUsages: KeyUsages[]): Promise<CryptoKey> {
return await crypto.subtle.importKey("raw", textToArrayBuffer(key), algorithm, true, keyUsages)
} }
export async function importJwk(key: JsonWebKey, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> { export async function importJwk(key: JsonWebKey, algorithm: SubtleCryptoImportKeyAlgorithm, keyUsages: KeyUsages[]): Promise<CryptoKey> {
return await crypto.subtle.importKey("jwk", key, algorithm, true, ["verify", "sign"]) return await crypto.subtle.importKey("jwk", key, algorithm, true, keyUsages)
} }
export async function importPublicKey(key: string, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> { export async function importPublicKey(key: string, algorithm: SubtleCryptoImportKeyAlgorithm, keyUsages: KeyUsages[]): Promise<CryptoKey> {
return await crypto.subtle.importKey("spki", pemToBinary(key), algorithm, true, ["verify"]) return await crypto.subtle.importKey("spki", pemToBinary(key), algorithm, true, keyUsages)
} }
export async function importPrivateKey(key: string, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> { export async function importPrivateKey(key: string, algorithm: SubtleCryptoImportKeyAlgorithm, keyUsages: KeyUsages[]): Promise<CryptoKey> {
return await crypto.subtle.importKey("pkcs8", pemToBinary(key), algorithm, true, ["sign"]) return await crypto.subtle.importKey("pkcs8", pemToBinary(key), algorithm, true, keyUsages)
} }
export async function importKey(key: string | JsonWebKey, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> { export async function importKey(key: string | JsonWebKey, algorithm: SubtleCryptoImportKeyAlgorithm, keyUsages: KeyUsages[]): Promise<CryptoKey> {
if (typeof key === 'object') if (typeof key === 'object')
return importJwk(key, algorithm) return importJwk(key, algorithm, keyUsages)
if (typeof key !== 'string') if (typeof key !== 'string')
throw new Error('Unsupported key type!') throw new Error('Unsupported key type!')
if (key.includes('PUBLIC')) if (key.includes('PUBLIC'))
return importPublicKey(key, algorithm) return importPublicKey(key, algorithm, keyUsages)
if (key.includes('PRIVATE')) if (key.includes('PRIVATE'))
return importPrivateKey(key, algorithm) return importPrivateKey(key, algorithm, keyUsages)
return importTextSecret(key, algorithm) return importTextSecret(key, algorithm, keyUsages)
} }
export function decodePayload<T = any>(raw: string): T | undefined { export function decodePayload<T = any>(raw: string): T | undefined {

View File

@@ -1,7 +1,4 @@
import crypto from 'node:crypto' import { describe, expect, test } from 'vitest'
Object.defineProperty(global, 'crypto', { value: { subtle: crypto.webcrypto.subtle }})
import { describe, expect, test } from '@jest/globals'
import jwt, { JwtAlgorithm } from '../src/index' import jwt, { JwtAlgorithm } from '../src/index'
type Dataset = { type Dataset = {

View File

@@ -1,4 +1,4 @@
import { describe, expect, test } from '@jest/globals' import { describe, expect, test } from 'vitest'
import { import {
bytesToByteString, bytesToByteString,
byteStringToBytes, byteStringToBytes,
@@ -67,7 +67,7 @@ describe('Imports', () => {
const testAlgorithm = { name: 'HMAC', hash: { name: 'SHA-256' } } const testAlgorithm = { name: 'HMAC', hash: { name: 'SHA-256' } }
const testCryptoKey = { type: 'secret', extractable: true, algorithm: { ...testAlgorithm, length: 168 }, usages: ['verify', 'sign'] } const testCryptoKey = { type: 'secret', extractable: true, algorithm: { ...testAlgorithm, length: 168 }, usages: ['verify', 'sign'] }
expect(await importTextSecret(testKey, testAlgorithm)).toMatchObject(testCryptoKey) expect(await importTextSecret(testKey, testAlgorithm, ['verify', 'sign'])).toMatchObject(testCryptoKey)
}) })
//test('importJwk', async () => {}) //test('importJwk', async () => {})

View File

@@ -5,6 +5,7 @@
"target": "esnext", "target": "esnext",
"lib": ["esnext"], "lib": ["esnext"],
"declaration": true, "declaration": true,
"emitDeclarationOnly": true,
"strict": true, "strict": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true,

9
vite.config.ts Normal file
View File

@@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'edge-runtime',
watch: false,
reporters: ['verbose']
}
})