1
0

Compare commits

..

11 Commits

Author SHA1 Message Date
Leo Developer
b05345279d allow using cryptokey directly 2024-01-18 20:47:05 +01:00
Richard Lee
55bc15bec4 Fix typing to make build work 2024-01-18 20:36:33 +01:00
kira924age
b0d4084a0f refactor: ๐Ÿ’ก reafactor decodePayload 2023-12-28 23:09:07 +01:00
kira924age
3fd594bbb5 feat: ๐ŸŽธ update decodePayload 2023-12-28 23:09:07 +01:00
kira924age
f8a216574a fix: ๐Ÿ› update decodePayload 2023-12-28 23:09:07 +01:00
kira924age
1f511549f5 feat: ๐ŸŽธ update decodePayload 2023-12-28 23:09:07 +01:00
Leo Developer
4be64469d3 fix header not being typed when using sign 2023-12-21 13:50:50 +01:00
72e64f1316 2.3.2 2023-11-16 20:34:51 +01:00
e235d835aa add generics to sign 2023-11-16 20:34:27 +01:00
03c66f4223 2.3.1 2023-11-16 20:23:58 +01:00
70488a6a48 update types and add generics to decode 2023-11-16 20:23:43 +01:00
4 changed files with 44 additions and 38 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@tsndr/cloudflare-worker-jwt",
"version": "2.3.0",
"version": "2.3.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@tsndr/cloudflare-worker-jwt",
"version": "2.3.0",
"version": "2.3.2",
"license": "MIT",
"devDependencies": {
"@cloudflare/workers-types": "^4.20231025.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@tsndr/cloudflare-worker-jwt",
"version": "2.3.0",
"version": "2.3.2",
"description": "A lightweight JWT implementation with ZERO dependencies for Cloudflare Worker",
"type": "module",
"exports": "./index.js",

View File

@@ -14,6 +14,11 @@ type Data = {
[key in JwtAlgorithm]: Dataset
}
type Payload = {
sub: string
name: string
}
const data: Data = {
'ES256': {
public: '-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9\nq9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n-----END PUBLIC KEY-----',
@@ -63,7 +68,7 @@ const data: Data = {
}
const payload = {
const payload: Payload = {
sub: "1234567890",
name: "John Doe",
}
@@ -77,7 +82,7 @@ describe.each(Object.entries(data) as [JwtAlgorithm, Dataset][])('%s', (algorith
})
test('decode external', async () => {
const decoded = jwt.decode(data.token)
const decoded = jwt.decode<Payload>(data.token)
expect({
sub: payload.sub,
name: payload.name
@@ -88,7 +93,7 @@ describe.each(Object.entries(data) as [JwtAlgorithm, Dataset][])('%s', (algorith
})
test('sign internal', async () => {
token = await jwt.sign(payload, data.private, algorithm)
token = await jwt.sign<Payload>(payload, data.private, algorithm)
expect(token).toMatch(/^[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+$/)
})

View File

@@ -3,14 +3,14 @@ if (typeof crypto === 'undefined' || !crypto.subtle)
/**
* @typedef JwtAlgorithm
* @type {'ES256'|'ES384'|'ES512'|'HS256'|'HS384'|'HS512'|'RS256'|'RS384'|'RS512'}
* @type {'ES256' | 'ES384' | 'ES512' | 'HS256' | 'HS384' | 'HS512' | 'RS256' | 'RS384' | 'RS512'}
*/
export type JwtAlgorithm = 'ES256'|'ES384'|'ES512'|'HS256'|'HS384'|'HS512'|'RS256'|'RS384'|'RS512'
export type JwtAlgorithm = 'ES256' | 'ES384' | 'ES512' | 'HS256' | 'HS384' | 'HS512' | 'RS256' | 'RS384' | 'RS512'
/**
* @typedef JwtAlgorithms
*/
export interface JwtAlgorithms {
export type JwtAlgorithms = {
[key: string]: SubtleCryptoImportKeyAlgorithm
}
@@ -18,16 +18,14 @@ export interface JwtAlgorithms {
* @typedef JwtHeader
* @prop {string} [typ] Type
*/
export interface JwtHeader {
export type JwtHeader<T = {}> = {
/**
* Type (default: `"JWT"`)
*
* @default "JWT"
*/
typ?: string
[key: string]: any
}
} & T
/**
* @typedef JwtPayload
@@ -39,7 +37,7 @@ export interface JwtHeader {
* @prop {string} [iat] Issued At
* @prop {string} [jti] JWT ID
*/
export interface JwtPayload {
export type JwtPayload<T = {}> = {
/** Issuer */
iss?: string
@@ -62,13 +60,13 @@ export interface JwtPayload {
jti?: string
[key: string]: any
}
} & T
/**
* @typedef JwtOptions
* @prop {JwtAlgorithm | string} algorithm
*/
export interface JwtOptions {
export type JwtOptions = {
algorithm?: JwtAlgorithm | string
}
@@ -77,32 +75,32 @@ export interface JwtOptions {
* @extends JwtOptions
* @prop {JwtHeader} [header]
*/
export interface JwtSignOptions extends JwtOptions {
header?: JwtHeader
}
export type JwtSignOptions<T> = {
header?: JwtHeader<T>
} & JwtOptions
/**
* @typedef JwtVerifyOptions
* @extends JwtOptions
* @prop {boolean} [throwError=false] If `true` throw error if checks fail. (default: `false`)
*/
export interface JwtVerifyOptions extends JwtOptions {
export type JwtVerifyOptions = {
/**
* If `true` throw error if checks fail. (default: `false`)
*
* @default false
*/
throwError?: boolean
}
} & JwtOptions
/**
* @typedef JwtData
* @prop {JwtHeader} header
* @prop {JwtPayload} payload
*/
export interface JwtData {
header?: JwtHeader
payload?: JwtPayload
export type JwtData<Payload = {}, Header = {}> = {
header?: JwtHeader<Header>
payload?: JwtPayload<Payload>
}
const algorithms: JwtAlgorithms = {
@@ -200,7 +198,10 @@ async function importKey(key: string | JsonWebKey, algorithm: SubtleCryptoImport
function decodePayload<T = any>(raw: string): T | undefined {
try {
return JSON.parse(atob(raw))
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
}
@@ -210,22 +211,22 @@ function decodePayload<T = any>(raw: string): T | undefined {
* 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 {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: JwtPayload, secret: string | JsonWebKey, options: JwtSignOptions | JwtAlgorithm = 'HS256'): Promise<string> {
export async function sign<Payload = {}, Header = {}>(payload: JwtPayload<Payload>, secret: string | JsonWebKey, options: JwtSignOptions<Header> | JwtAlgorithm = 'HS256'): Promise<string> {
if (typeof options === 'string')
options = { algorithm: options }
options = { algorithm: 'HS256', header: { typ: 'JWT' }, ...options }
options = { algorithm: 'HS256', header: { typ: 'JWT' } as JwtHeader<Header>, ...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')
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')
@@ -240,7 +241,7 @@ export async function sign(payload: JwtPayload, secret: string | JsonWebKey, opt
const partialToken = `${textToBase64Url(JSON.stringify({ ...options.header, alg: options.algorithm }))}.${textToBase64Url(JSON.stringify(payload))}`
const key = await importKey(secret, algorithm)
const key = secret instanceof CryptoKey ? secret : await importKey(secret, algorithm)
const signature = await crypto.subtle.sign(algorithm, key, textToArrayBuffer(partialToken))
return `${partialToken}.${arrayBufferToBase64Url(signature)}`
@@ -250,12 +251,12 @@ export async function sign(payload: JwtPayload, secret: string | JsonWebKey, opt
* 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 {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: string, secret: string | JsonWebKey, options: JwtVerifyOptions | JwtAlgorithm = { algorithm: 'HS256', throwError: false }): Promise<boolean> {
export async function verify(token: string, secret: string | JsonWebKey | CryptoKey, options: JwtVerifyOptions | JwtAlgorithm = { algorithm: 'HS256', throwError: false }): Promise<boolean> {
if (typeof options === 'string')
options = { algorithm: options, throwError: false }
@@ -265,7 +266,7 @@ export async function verify(token: string, secret: string | JsonWebKey, options
throw new Error('token must be a string')
if (typeof secret !== 'string' && typeof secret !== 'object')
throw new Error('secret must be a string or a JWK 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')
@@ -292,7 +293,7 @@ export async function verify(token: string, secret: string | JsonWebKey, options
if (payload.exp && payload.exp <= Math.floor(Date.now() / 1000))
throw new Error('EXPIRED')
const key = await importKey(secret, algorithm)
const key = secret instanceof CryptoKey ? secret : await importKey(secret, algorithm)
return await crypto.subtle.verify(algorithm, key, base64UrlToArrayBuffer(tokenParts[2]), textToArrayBuffer(`${tokenParts[0]}.${tokenParts[1]}`))
} catch(err) {
@@ -309,10 +310,10 @@ export async function verify(token: string, secret: string | JsonWebKey, options
* @param {string} token The token string generated by `jwt.sign()`.
* @returns {JwtData} Returns an `object` containing `header` and `payload`.
*/
export function decode(token: string): JwtData {
export function decode<Payload = {}, Header = {}>(token: string): JwtData<Payload, Header> {
return {
header: decodePayload<JwtHeader>(token.split('.')[0].replace(/-/g, '+').replace(/_/g, '/')),
payload: decodePayload<JwtPayload>(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'))
header: decodePayload<JwtHeader<Header>>(token.split('.')[0].replace(/-/g, '+').replace(/_/g, '/')),
payload: decodePayload<JwtPayload<Payload>>(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'))
}
}
@@ -320,4 +321,4 @@ export default {
sign,
verify,
decode
}
}