working on more tests
This commit is contained in:
@@ -122,4 +122,4 @@ describe.each(Object.entries(data) as [JwtAlgorithm, Dataset][])('%s', (algorith
|
|||||||
const verified = await jwt.verify(token, data.public, algorithm)
|
const verified = await jwt.verify(token, data.public, algorithm)
|
||||||
expect(verified).toBeTruthy()
|
expect(verified).toBeTruthy()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
104
src/index.ts
104
src/index.ts
@@ -1,3 +1,12 @@
|
|||||||
|
import {
|
||||||
|
textToArrayBuffer,
|
||||||
|
arrayBufferToBase64Url,
|
||||||
|
base64UrlToArrayBuffer,
|
||||||
|
textToBase64Url,
|
||||||
|
importKey,
|
||||||
|
decodePayload
|
||||||
|
} from "./utils"
|
||||||
|
|
||||||
if (typeof crypto === 'undefined' || !crypto.subtle)
|
if (typeof crypto === 'undefined' || !crypto.subtle)
|
||||||
throw new Error('SubtleCrypto not supported!')
|
throw new Error('SubtleCrypto not supported!')
|
||||||
|
|
||||||
@@ -115,101 +124,6 @@ const algorithms: JwtAlgorithms = {
|
|||||||
RS512: { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-512' } }
|
RS512: { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-512' } }
|
||||||
}
|
}
|
||||||
|
|
||||||
function bytesToByteString(bytes: Uint8Array): string {
|
|
||||||
let byteStr = ''
|
|
||||||
for (let i = 0; i < bytes.byteLength; i++) {
|
|
||||||
byteStr += String.fromCharCode(bytes[i])
|
|
||||||
}
|
|
||||||
return byteStr
|
|
||||||
}
|
|
||||||
|
|
||||||
function byteStringToBytes(byteStr: string): Uint8Array {
|
|
||||||
let bytes = new Uint8Array(byteStr.length)
|
|
||||||
for (let i = 0; i < byteStr.length; i++) {
|
|
||||||
bytes[i] = byteStr.charCodeAt(i)
|
|
||||||
}
|
|
||||||
return bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
function arrayBufferToBase64String(arrayBuffer: ArrayBuffer): string {
|
|
||||||
return btoa(bytesToByteString(new Uint8Array(arrayBuffer)))
|
|
||||||
}
|
|
||||||
|
|
||||||
function base64StringToArrayBuffer(b64str: string): ArrayBuffer {
|
|
||||||
return byteStringToBytes(atob(b64str)).buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
function textToArrayBuffer(str: string): ArrayBuffer {
|
|
||||||
return byteStringToBytes(decodeURI(encodeURIComponent(str)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
function arrayBufferToText(arrayBuffer: ArrayBuffer): string {
|
|
||||||
return bytesToByteString(new Uint8Array(arrayBuffer))
|
|
||||||
}
|
|
||||||
|
|
||||||
function arrayBufferToBase64Url(arrayBuffer: ArrayBuffer): string {
|
|
||||||
return arrayBufferToBase64String(arrayBuffer).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
|
|
||||||
}
|
|
||||||
|
|
||||||
function base64UrlToArrayBuffer(b64url: string): ArrayBuffer {
|
|
||||||
return base64StringToArrayBuffer(b64url.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, ''))
|
|
||||||
}
|
|
||||||
|
|
||||||
function textToBase64Url(str: string): string {
|
|
||||||
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: string): ArrayBuffer {
|
|
||||||
return base64StringToArrayBuffer(pem.replace(/-+(BEGIN|END).*/g, '').replace(/\s/g, ''))
|
|
||||||
}
|
|
||||||
|
|
||||||
async function importTextSecret(key: string, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> {
|
|
||||||
return await crypto.subtle.importKey("raw", textToArrayBuffer(key), algorithm, true, ["verify", "sign"])
|
|
||||||
}
|
|
||||||
|
|
||||||
async function importJwk(key: JsonWebKey, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> {
|
|
||||||
return await crypto.subtle.importKey("jwk", key, algorithm, true, ["verify", "sign"])
|
|
||||||
}
|
|
||||||
|
|
||||||
async function importPublicKey(key: string, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> {
|
|
||||||
return await crypto.subtle.importKey("spki", pemToBinary(key), algorithm, true, ["verify"])
|
|
||||||
}
|
|
||||||
|
|
||||||
async function importPrivateKey(key: string, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> {
|
|
||||||
return await crypto.subtle.importKey("pkcs8", pemToBinary(key), algorithm, true, ["sign"])
|
|
||||||
}
|
|
||||||
|
|
||||||
async function importKey(key: string | JsonWebKey, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> {
|
|
||||||
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<T = any>(raw: string): T | undefined {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs a payload and returns the token
|
* Signs a payload and returns the token
|
||||||
*
|
*
|
||||||
|
|||||||
81
src/utils.spec.ts
Normal file
81
src/utils.spec.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { describe, expect, test } from '@jest/globals'
|
||||||
|
import {
|
||||||
|
bytesToByteString,
|
||||||
|
byteStringToBytes,
|
||||||
|
arrayBufferToBase64String,
|
||||||
|
base64StringToArrayBuffer,
|
||||||
|
textToArrayBuffer,
|
||||||
|
arrayBufferToText,
|
||||||
|
arrayBufferToBase64Url,
|
||||||
|
base64UrlToArrayBuffer,
|
||||||
|
textToBase64Url,
|
||||||
|
pemToBinary,
|
||||||
|
importTextSecret
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
describe('Converters', () => {
|
||||||
|
const testString = 'cloudflare-worker-jwt'
|
||||||
|
const testByteArray = [ 99, 108, 111, 117, 100, 102, 108, 97, 114, 101, 45, 119, 111, 114, 107, 101, 114, 45, 106, 119, 116 ]
|
||||||
|
const testUint8Array = new Uint8Array(testByteArray)
|
||||||
|
const testBase64String = 'Y2xvdWRmbGFyZS13b3JrZXItand0'
|
||||||
|
const testArrayBuffer = testUint8Array.buffer
|
||||||
|
|
||||||
|
test('bytesToByteString', () => {
|
||||||
|
expect(bytesToByteString(testUint8Array)).toStrictEqual(testString)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('byteStringToBytes', () => {
|
||||||
|
expect(byteStringToBytes(testString)).toStrictEqual(testUint8Array)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('arrayBufferToBase64String', () => {
|
||||||
|
expect(arrayBufferToBase64String(testArrayBuffer)).toStrictEqual(testBase64String)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('base64StringToArrayBuffer', () => {
|
||||||
|
expect(base64StringToArrayBuffer(testBase64String)).toStrictEqual(testArrayBuffer)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('textToArrayBuffer', () => {
|
||||||
|
expect(textToArrayBuffer(testString)).toStrictEqual(testUint8Array)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('arrayBufferToText', () => {
|
||||||
|
expect(arrayBufferToText(testArrayBuffer)).toStrictEqual(testString)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('arrayBufferToBase64Url', () => {
|
||||||
|
expect(arrayBufferToBase64Url(testArrayBuffer)).toStrictEqual(testBase64String)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('base64UrlToArrayBuffer', () => {
|
||||||
|
expect(base64UrlToArrayBuffer(testBase64String)).toStrictEqual(testArrayBuffer)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('textToBase64Url', () => {
|
||||||
|
expect(textToBase64Url(testString)).toStrictEqual(testBase64String)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('pemToBinary', () => {
|
||||||
|
expect(pemToBinary(`-----BEGIN PUBLIC KEY-----\n${testBase64String}\n-----END PUBLIC KEY-----`)).toStrictEqual(testArrayBuffer)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Imports', () => {
|
||||||
|
test('importTextSecret', async () => {
|
||||||
|
const testKey = 'cloudflare-worker-jwt'
|
||||||
|
const testAlgorithm = { name: 'HMAC', hash: { name: 'SHA-256' } }
|
||||||
|
const testCryptoKey = { type: 'secret', extractable: true, algorithm: { ...testAlgorithm, length: 168 }, usages: ['verify', 'sign'] }
|
||||||
|
|
||||||
|
expect(await importTextSecret(testKey, testAlgorithm)).toMatchObject(testCryptoKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
//test('importJwk', async () => {})
|
||||||
|
//test('importPublicKey', async () => {})
|
||||||
|
//test('importPrivateKey', async () => {})
|
||||||
|
//test('importKey', async () => {})
|
||||||
|
})
|
||||||
|
|
||||||
|
//describe('Payload', () => {
|
||||||
|
// test('decodePayload', () => {})
|
||||||
|
//})
|
||||||
93
src/utils.ts
Normal file
93
src/utils.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
export function bytesToByteString(bytes: Uint8Array): string {
|
||||||
|
let byteStr = ''
|
||||||
|
for (let i = 0; i < bytes.byteLength; i++) {
|
||||||
|
byteStr += String.fromCharCode(bytes[i])
|
||||||
|
}
|
||||||
|
return byteStr
|
||||||
|
}
|
||||||
|
|
||||||
|
export function byteStringToBytes(byteStr: string): Uint8Array {
|
||||||
|
let bytes = new Uint8Array(byteStr.length)
|
||||||
|
for (let i = 0; i < byteStr.length; i++) {
|
||||||
|
bytes[i] = byteStr.charCodeAt(i)
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
export function arrayBufferToBase64String(arrayBuffer: ArrayBuffer): string {
|
||||||
|
return btoa(bytesToByteString(new Uint8Array(arrayBuffer)))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64StringToArrayBuffer(b64str: string): ArrayBuffer {
|
||||||
|
return byteStringToBytes(atob(b64str)).buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
export function textToArrayBuffer(str: string): ArrayBuffer {
|
||||||
|
return byteStringToBytes(decodeURI(encodeURIComponent(str)))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function arrayBufferToText(arrayBuffer: ArrayBuffer): string {
|
||||||
|
return bytesToByteString(new Uint8Array(arrayBuffer))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function arrayBufferToBase64Url(arrayBuffer: ArrayBuffer): string {
|
||||||
|
return arrayBufferToBase64String(arrayBuffer).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64UrlToArrayBuffer(b64url: string): ArrayBuffer {
|
||||||
|
return base64StringToArrayBuffer(b64url.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, ''))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function textToBase64Url(str: string): string {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const charCodes = encoder.encode(str);
|
||||||
|
const binaryStr = String.fromCharCode(...charCodes);
|
||||||
|
return btoa(binaryStr).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pemToBinary(pem: string): ArrayBuffer {
|
||||||
|
return base64StringToArrayBuffer(pem.replace(/-+(BEGIN|END).*/g, '').replace(/\s/g, ''))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function importTextSecret(key: string, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> {
|
||||||
|
return await crypto.subtle.importKey("raw", textToArrayBuffer(key), algorithm, true, ["verify", "sign"])
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function importJwk(key: JsonWebKey, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> {
|
||||||
|
return await crypto.subtle.importKey("jwk", key, algorithm, true, ["verify", "sign"])
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function importPublicKey(key: string, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> {
|
||||||
|
return await crypto.subtle.importKey("spki", pemToBinary(key), algorithm, true, ["verify"])
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function importPrivateKey(key: string, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> {
|
||||||
|
return await crypto.subtle.importKey("pkcs8", pemToBinary(key), algorithm, true, ["sign"])
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function importKey(key: string | JsonWebKey, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodePayload<T = any>(raw: string): T | undefined {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user