1
0

Compare commits

..

28 Commits

Author SHA1 Message Date
f846695242 Update docs 2022-06-04 17:29:50 +02:00
695e1c0dfe .decode() syntax change to support headers 2022-06-04 17:23:53 +02:00
64da4c625f .verify() bugfix 2022-06-04 14:53:59 +02:00
e4038ae0a7 Just to make sure 2022-06-04 14:40:06 +02:00
44329617de Fix .verify() and add testing 2022-06-04 14:18:06 +02:00
Toby Schneider
e7964b63c2 Merge pull request #9 from alaister/main
Add support for JWT header
2022-06-01 20:10:51 +02:00
b733a0650d Update readme 2022-06-01 15:11:57 +02:00
43879de15e Implement throwError option for .verify(). 2022-06-01 15:09:25 +02:00
0c8f476751 Update to v1.1.7 2022-04-11 02:20:54 +02:00
3c5d178fec Fix timestamp check 2022-04-11 02:20:14 +02:00
Alaister Young
f12cafd9d0 Add support for JWT header 2022-03-01 10:21:32 +11:00
e0219ff21f Update to v1.1.6 2022-02-27 16:15:58 +01:00
Toby Schneider
bc7fa845ed Merge pull request #7 from plesiv/add-rsa-algorithm
Add support for RSA algorithm
2022-02-27 15:56:13 +01:00
Toby Schneider
5ee043e597 Merge pull request #8 from workeffortwaste/fix-constructor-error
Fix constructor error
2022-02-27 15:55:38 +01:00
Chris Johnson
9c52217ca2 Fix constructor error 2022-02-24 09:17:40 +00:00
Zoran Plesivcak
5160cfa416 Add support for RSA algorithm 2022-02-13 23:58:16 +00:00
430fa0eb84 Update to 1.1.5 2021-11-02 13:58:16 +01:00
4d4cf316d0 Change error message 2021-11-02 13:56:12 +01:00
e244ff0618 Added more algorithms, keyid and did some cleanup 2021-10-27 01:22:01 +02:00
Toby Schneider
13943f64d7 Create FUNDING.yml 2021-10-26 23:54:37 +02:00
8952a12e4f Updated example code 2021-09-23 22:51:14 +02:00
af3ea4cf8d Rearranged readme 2021-05-26 19:16:25 +02:00
d99e361d65 Bump version 2021-05-26 02:19:23 +02:00
79f030a35a Added JSDoc 2021-05-26 02:19:04 +02:00
5fd472c33d Renamed workflow 2021-05-25 22:44:42 +02:00
54ad90f6fd Updated tslint 2021-05-25 22:44:10 +02:00
8d9f70ca36 Added nbf and exp support 2021-05-23 22:41:35 +02:00
e34e1aedab Cleaned up 2021-05-23 18:33:04 +02:00
10 changed files with 8331 additions and 337 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: tsndr

View File

@@ -1,19 +0,0 @@
name: Lint
on:
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: 15.x
- run: npm ci
- run: npm run lint --if-present

View File

@@ -1,4 +1,4 @@
name: Publish NPM Package
name: Publish
on:
release:

119
README.md
View File

@@ -2,38 +2,80 @@
A lightweight JWT implementation with ZERO dependencies for Cloudflare Workers.
## Contents
- [Install](#install)
- [Examples](#examples)
- [Usage](#usage)
## Install
```
npm i @tsndr/cloudflare-worker-jwt
npm i -D @tsndr/cloudflare-worker-jwt
```
## Examples
### Basic Example
```javascript
async () => {
const jwt = require('@tsndr/cloudflare-worker-jwt')
// Creating a token
const token = await jwt.sign({ name: 'John Doe', email: 'john.doe@gmail.com' }, 'secret')
// Verifing token
const isValid = await jwt.verify(token, 'secret')
// Check for validity
if (!isValid)
return
// Decoding token
const { payload } = jwt.decode(token)
}
```
### Restrict Timeframe
```javascript
async () => {
const jwt = require('@tsndr/cloudflare-worker-jwt')
// Creating a token
const token = await jwt.sign({
name: 'John Doe',
email: 'john.doe@gmail.com',
nbf: Math.floor(Date.now() / 1000) + (60 * 60), // Not before: Now + 1h
exp: Math.floor(Date.now() / 1000) + (2 * (60 * 60)) // Expires: Now + 2h
}, 'secret')
// Verifing token
const isValid = await jwt.verify(token, 'secret') // false
// Check for validity
if (!isValid)
return
// Decoding token
const { payload } = jwt.decode(token) // { name: 'John Doe', email: 'john.doe@gmail.com', ... }
}
```
## Usage
### Simple Example
```javascript
const jwt = require('@tsndr/cloudflare-worker-jwt')
// Creating a token
const token = jwt.sign({ name: 'John Doe', email: 'john.doe@gmail.com' }, 'secret')
// Verifing token
const isValid = jwt.verify(token, 'secret')
// Decoding token
const payload = jwt.decode(token)
```
- [Sign](#sign)
- [Verify](#verify)
- [Decode](#decode)
<hr>
### `jwt.sign(payload, secret, [algorithm])`
### Sign
#### `jwt.sign(payload, secret, [options])`
Signs a payload and returns the token.
@@ -41,16 +83,17 @@ Signs a payload and returns the token.
Argument | Type | Satus | Default | Description
----------- | -------- | -------- | ------- | -----------
`payload` | `object` | required | - | The payload object
`payload` | `object` | required | - | The payload object. To use `nbf` (Not Before) and/or `exp` (Expiration Time) add `nbf` and/or `exp` to the payload.
`secret` | `string` | required | - | A string which is used to sign the payload.
`algorithm` | `string` | optional | `HS256` | The algorithm used to sign the payload, possible values: `HS256` or `HS512`
`options` | `object`, `string` | optional | `{ algorithm: 'HS256' }` | The options object supporting `algorithm` and `keyid` or just the algorithm string. (See [Available Algorithms](#available-algorithms))
#### `return`
returns token as a `string`
Returns token as a `string`.
<hr>
### `jwt.verify(token, secret, [algorithm])`
### Verify
#### `jwt.verify(token, secret, [options])`
Verifies the integrity of the token and returns a boolean value.
@@ -58,20 +101,44 @@ Argument | Type | Satus | Default | Description
----------- | -------- | -------- | ------- | -----------
`token` | `string` | required | - | The token string generated by `jwt.sign()`.
`secret` | `string` | required | - | The string which was used to sign the payload.
`algorithm` | `string` | optional | `HS256` | The algorithm used to sign the payload, possible values: `HS256` or `HS512`
`algorithm` | `object`, `string` | optional | `{ algorithm: 'HS256', throwError: false }` | The options object supporting `algorithm` or just the algorithm string. (See [Available Algorithms](#available-algorithms))
#### `return`
returns `boolean`
Returns `true` if signature, `nbf` (if set) and `exp` (if set) are valid, otherwise returns `false`.
<hr>
### `jwt.decode(token)`
### Decode
#### `jwt.decode(token)`
Returns the payload without verifying the integrity of the token.
Returns the payload **without** verifying the integrity of the token. Please use `jwt.verify()` first to keep your application secure!
Argument | Type | Satus | Default | Description
----------- | -------- | -------- | ------- | -----------
`token` | `string` | required | - | The token string generated by `jwt.sign()`.
#### `return`
returns payload `object`
Returns an `object` containing `header` and `payload`:
```javascript
{
header: {
alg: 'HS256',
typ: 'JWT'
},
payload: {
name: 'John Doe',
email: 'john.doe@gmail.com'
}
}
```
### Available Algorithms
- ES256
- ES384
- ES512
- HS256
- HS384
- HS512
- RS256
- RS384
- RS512

59
index.d.ts vendored
View File

@@ -1,7 +1,60 @@
/**
* JWT
*
* @class
* @constructor
* @public
*/
declare class JWT {
sign(payload: object, secret: string, algorithm?: "HS256" | "HS512"): Promise<string>
verify(token: string, secret: string, algorithm?: "HS256" | "HS512"): Promise<boolean>
decode(token: string): object | null
/**
* Signs a payload and returns the token
*
* @param {object} payload The payload object. To use `nbf` (Not Before) and/or `exp` (Expiration Time) add `nbf` and/or `exp` to the payload.
* @param {string} secret A string which is used to sign the payload.
* @param {JWTSignOptions | JWTAlgorithm} options The options object or the algorithm.
* @throws {Error} If there's a validation issue.
* @returns {Promise<string>} Returns token as a `string`.
*/
sign(payload: object, secret: string, options?: JWTSignOptions | JWTAlgorithm): Promise<string>
/**
* Verifies the integrity of the token and returns a boolean value.
*
* @param {string} token The token string generated by `jwt.sign()`.
* @param {string} 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`.
*/
verify(token: string, secret: string, 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!
*
* @param {string} token The token string generated by `jwt.sign()`.
* @returns {JWTDecodeReturn} Returns an `object` containing `header` and `payload`.
*/
decode(token: string): JWTDecodeReturn
}
declare const _exports: JWT
type JWTAlgorithm = 'ES256' | 'ES384' | 'ES512' | 'HS256' | 'HS384' | 'HS512' | 'RS256' | 'RS384' | 'RS512'
type JWTSignOptions = {
algorithm?: JWTAlgorithm,
keyid?: string
header?: object
}
type JWTVerifyOptions = {
algorithm?: JWTAlgorithm
throwError?: boolean
}
type JWTDecodeReturn = {
header: object,
payload: object
}
export = _exports

154
index.js
View File

@@ -9,92 +9,118 @@ class Base64URL {
class JWT {
constructor() {
if (!crypto || !crypto.subtle)
if (typeof crypto === 'undefined' || !crypto.subtle)
throw new Error('Crypto not supported!')
this.algorithms = {
HS256: {
name: 'HMAC',
hash: {
name: 'SHA-256'
}
},
HS512: {
name: 'HMAC',
hash: {
name: 'SHA-512'
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' } },
}
}
}
}
utf8ToUint8Array(str) {
_utf8ToUint8Array(str) {
return Base64URL.parse(btoa(unescape(encodeURIComponent(str))))
}
async sign(payload, secret, algorithm = 'HS256') {
if (payload === null || typeof payload !== 'object')
throw new Error('payload must be an object')
if (typeof secret !== 'string')
throw new Error('secret must be a string')
if (typeof algorithm !== 'string')
throw new Error('algorithm must be a string')
const importAlgorithm = this.algorithms[algorithm]
if (!importAlgorithm)
throw new Error('algorithm not found')
const payloadAsJSON = JSON.stringify(payload)
const partialToken = `${Base64URL.stringify(this.utf8ToUint8Array(JSON.stringify({ alg: algorithm, typ: 'JWT' })))}.${Base64URL.stringify(this.utf8ToUint8Array(payloadAsJSON))}`
const key = await crypto.subtle.importKey('raw', this.utf8ToUint8Array(secret), importAlgorithm, false, ['sign'])
const characters = payloadAsJSON.split('')
const it = this.utf8ToUint8Array(payloadAsJSON).entries()
let i = 0
const result = []
let current
while (!(current = it.next()).done) {
result.push([current.value[1], characters[i]])
i++
_str2ab(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);
}
const signature = await crypto.subtle.sign(importAlgorithm.name, key, this.utf8ToUint8Array(partialToken))
return `${partialToken}.${Base64URL.stringify(new Uint8Array(signature))}`
return buf;
}
async verify(token, secret, algorithm = 'HS256') {
if (typeof token !== 'string')
throw new Error('token must be a string')
if (typeof secret !== 'string')
throw new Error('secret must be a string')
if (typeof algorithm !== 'string')
throw new Error('algorithm must be a string')
const tokenParts = token.split('.')
if (tokenParts.length !== 3)
throw new Error('token must have 3 parts')
const importAlgorithm = this.algorithms[algorithm]
if (!importAlgorithm)
throw new Error('algorithm not found')
const keyData = this.utf8ToUint8Array(secret)
const key = await crypto.subtle.importKey('raw', keyData, importAlgorithm, false, ['sign'])
const partialToken = tokenParts.slice(0, 2).join('.')
const signaturePart = tokenParts[2]
const messageAsUint8Array = this.utf8ToUint8Array(partialToken)
const res = await crypto.subtle.sign(importAlgorithm.name, key, messageAsUint8Array)
return Base64URL.stringify(new Uint8Array(res)) === signaturePart
}
decode(token) {
let output = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')
switch (output.length % 4) {
_decodePayload(raw) {
switch (raw.length % 4) {
case 0:
break
case 2:
output += '=='
raw += '=='
break
case 3:
output += '='
raw += '='
break
default:
throw new Error('Illegal base64url string!')
}
try {
return JSON.parse(decodeURIComponent(escape(atob(output))))
return JSON.parse(decodeURIComponent(escape(atob(raw))))
} catch {
return null
}
}
async sign(payload, secret, options = { algorithm: 'HS256', header: { typ: 'JWT' } }) {
if (typeof options === 'string')
options = { algorithm: options, header: { typ: 'JWT' } }
if (payload === null || typeof payload !== 'object')
throw new Error('payload must be an object')
if (typeof secret !== 'string')
throw new Error('secret must be a string')
if (typeof options.algorithm !== 'string')
throw new Error('options.algorithm must be a string')
const importAlgorithm = this.algorithms[options.algorithm]
if (!importAlgorithm)
throw new Error('algorithm not found')
payload.iat = Math.floor(Date.now() / 1000)
const payloadAsJSON = JSON.stringify(payload)
const partialToken = `${Base64URL.stringify(this._utf8ToUint8Array(JSON.stringify({ ...options.header, alg: options.algorithm, kid: options.keyid })))}.${Base64URL.stringify(this._utf8ToUint8Array(payloadAsJSON))}`
let keyFormat = 'raw'
let keyData
if (secret.startsWith('-----BEGIN')) {
keyFormat = 'pkcs8'
keyData = this._str2ab(atob(secret.replace(/-----BEGIN.*?-----/g, '').replace(/-----END.*?-----/g, '').replace(/\s/g, '')))
} else
keyData = this._utf8ToUint8Array(secret)
const key = await crypto.subtle.importKey(keyFormat, keyData, importAlgorithm, false, ['sign'])
const signature = await crypto.subtle.sign(importAlgorithm, key, this._utf8ToUint8Array(partialToken))
return `${partialToken}.${Base64URL.stringify(new Uint8Array(signature))}`
}
async verify(token, secret, options = { algorithm: 'HS256', throwError: false }) {
if (typeof options === 'string')
options = { algorithm: options }
if (typeof token !== 'string')
throw new Error('token must be a string')
if (typeof secret !== 'string')
throw new Error('secret must be a string')
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')
const importAlgorithm = this.algorithms[options.algorithm]
if (!importAlgorithm)
throw new Error('algorithm not found')
const payload = this.decode(token)
if (payload.nbf && payload.nbf > Math.floor(Date.now() / 1000)) {
if (options.throwError)
throw 'NOT_YET_VALID'
return false
}
if (payload.exp && payload.exp <= Math.floor(Date.now() / 1000)) {
if (options.throwError)
throw 'EXPIRED'
return false
}
let keyFormat = 'raw'
let keyData
if (secret.startsWith('-----BEGIN')) {
keyFormat = 'spki'
keyData = this._str2ab(atob(secret.replace(/-----BEGIN.*?-----/g, '').replace(/-----END.*?-----/g, '').replace(/\s/g, '')))
} else
keyData = this._utf8ToUint8Array(secret)
const key = await crypto.subtle.importKey(keyFormat, keyData, importAlgorithm, false, ['verify'])
return await crypto.subtle.verify(importAlgorithm, key, Base64URL.parse(tokenParts[2]), this._utf8ToUint8Array(`${tokenParts[0]}.${tokenParts[1]}`))
}
decode(token) {
return {
header: this._decodePayload(token.split('.')[0].replace(/-/g, '+').replace(/_/g, '/')),
payload: this._decodePayload(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'))
}
}
}
module.exports = new JWT

168
index.test.js Normal file
View File

@@ -0,0 +1,168 @@
const { subtle } = require('node:crypto').webcrypto
Object.defineProperty(global, 'crypto', {
value: { subtle }
})
const JWT = require('./index')
const oneDay = (60 * 60 * 24)
const now = Date.now() / 1000
const secrets = {}
// Keypairs
for (const algorithm of Object.keys(JWT.algorithms)) {
if (algorithm.startsWith('HS'))
secrets[algorithm] = 'secret'
else if (algorithm.startsWith('RS')) {
secrets[algorithm] = {
public: `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
mwIDAQAB
-----END PUBLIC KEY-----`,
private: `-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKj
MzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvu
NMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZ
qgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulg
p2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlR
ZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwi
VuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskV
laAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8
sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83H
mQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwY
dgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cw
ta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQ
DM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2T
N0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t
0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPv
t8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDU
AhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk
48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISL
DY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnK
xt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEA
mNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh
2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfz
et6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhr
VBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicD
TQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cnc
dn/RsYEONbwQSjIfMPkvxF+8HQ==
-----END PRIVATE KEY-----`
}
} else if (algorithm.startsWith('ES')) {
if (algorithm === 'ES256') {
secrets[algorithm] = {
public: `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
-----END PUBLIC KEY-----`,
private: `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
-----END PRIVATE KEY-----`
}
} else if (algorithm === 'ES384') {
secrets[algorithm] = {
public: `-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+
Pk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii
1D3jaW6pmGVJFhodzC31cy5sfOYotrzF
-----END PUBLIC KEY-----`,
private: `-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCAHpFQ62QnGCEvYh/p
E9QmR1C9aLcDItRbslbmhen/h1tt8AyMhskeenT+rAyyPhGhZANiAAQLW5ZJePZz
MIPAxMtZXkEWbDF0zo9f2n4+T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw
8lE5IPUWpgu553SteKigiKLUPeNpbqmYZUkWGh3MLfVzLmx85ii2vMU=
-----END PRIVATE KEY-----`
}
} else if (algorithm === 'ES512') {
secrets[algorithm] = {
public: `-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ
PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47
6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM
Al8G7CqwoJOsW7Kddns=
-----END PUBLIC KEY-----`,
private: `-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBiyAa7aRHFDCh2qga
9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx0pDrmCV9mbroFtfEa0XVfKuMAxxf
Z6LM/yKhgYkDgYYABAGBzgdnP798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPN
v3SchO0lRw9Ru86x1khnVDx+duq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrear
jMiZNE25pT2yWP1NUndJxPcvVtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12
ew==
-----END PRIVATE KEY-----`
}
}
}
}
// Payload
const testPayload = {
sub: "1234567890",
name: "John Doe"
}
// Self test
test.each(Object.entries(secrets))(`Self test: %s`, async (algorithm, key) => {
let privateKey = key
let publicKey = key
if (typeof key === 'object') {
privateKey = key.private
publicKey = key.public
}
const token = await JWT.sign(testPayload, privateKey, { algorithm })
expect(token).toMatch(/^[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+$/)
const verified = await JWT.verify(token, publicKey, { algorithm })
expect(verified).toBeTruthy()
const { payload } = JWT.decode(token)
expect({
sub: payload.sub,
name: payload.name
}).toMatchObject({
sub: testPayload.sub,
name: testPayload.name
})
})
// External token test
const externalTokens = {
ES256: 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.Ri9u3ZJpyoHf1_i3KpVE5gMggyU3VMYPeEVktAsG1kGLOxFNJBXydQls3WFBaXXH2-sN74IMe-nDcM7NoJ6GMQ',
ES384: 'eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.6nmlvcCpsfENb6ssgX3rJ2XSjpFSXD1RPS1CK0iDZFdi6I6Gnmpi456RSW6-0XSSgq2E2XBcWCSYE6TeI63jOGZJTQ6-65g4sndbzBPqYPWbLny00NQ4MQgQXVu6tRzg',
ES512: 'eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.AbRwaCPJM2X3XjE2kInClHVGJNIcpL5C4a1ZrgwEM05RTryyNbazWRFbXAEDtHm8crvXpqw3a8JQwYDwvMyoOr4jAJ4RbhJitLgCGEzhKjvNy4xGrg3dV8gGEShFowgDfVz0KqHOX_Bc_DbyL-gtZPdfGTwT2upLkJE-lj47RStPDh0b',
HS256: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o',
HS384: 'eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.hO2sthNQUSfvI9ylUdMKDxcrm8jB3KL6Rtkd3FOskL-jVqYh2CK1es8FKCQO8_tW',
HS512: 'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.wUVS6tazE2N98_J4SH_djkEe1igXPu0qILAvVXCiO6O20gdf5vZ2sYFWX3c-Hy6L4TD47b3DSAAO9XjSqpJfag',
RS256: 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.Eci61G6w4zh_u9oOCk_v1M_sKcgk0svOmW4ZsL-rt4ojGUH2QY110bQTYNwbEVlowW7phCg7vluX_MCKVwJkxJT6tMk2Ij3Plad96Jf2G2mMsKbxkC-prvjvQkBFYWrYnKWClPBRCyIcG0dVfBvqZ8Mro3t5bX59IKwQ3WZ7AtGBYz5BSiBlrKkp6J1UmP_bFV3eEzIHEFgzRa3pbr4ol4TK6SnAoF88rLr2NhEz9vpdHglUMlOBQiqcZwqrI-Z4XDyDzvnrpujIToiepq9bCimPgVkP54VoZzy-mMSGbthYpLqsL_4MQXaI1Uf_wKFAUuAtzVn4-ebgsKOpvKNzVA',
RS384: 'eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.oPvWzaCp8xUpt5mHhPSn0qLsZfCFj0NVmb4mz4dFQPCCMj-F5zVn9e3zZoj0lIXWM8rxB69QHC3Er47mtDt3BKgysTL3BvvV89kD6UjLoUcAI3lwj0mi7acLoE27i1_TnIBqWNRPAsdvTDawNE0_4lvI5bxEWQCqisJwxCoMDIeJsmDzfyApgU_SAFSVULxXwU2VewaxdQB-41OZdWwUEAxh81iB6DFWrqd2CaJkUYoWjgYpeWsyeC2m_-ECGrHGEz1nKTm9c7BaPxurz7fHD7RJd9Wpx-mKDVsfspO9quWb_OLeGGbxTtAomMvjQjut56kx2fqTleDnNDh_0GE88w',
RS512: 'eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.kEZmDAnHHU_0bcGXMd5LA7vF87yQgXNaioPHP4lU4O3JJYuZ54fJdv3HT58xk-MFEDuWro_5fvNIp2VM-PlkZvYWrhQkJ-c-seoSa3ANq_PciC3bGfzYHEdjAE71GrAMI4FlcAGsq3ChkOnCTFqjWDmVwaRYCgMsFQ-U5cjvFhndFMizrkRljTF4v5oFdWytV_J-UafPtNdQXcGND1M74DqObnTHhZHg8aDfNzZcvnIeKcDVGUlUEL5ia1kPMrVhCtOAOJmEU8ivCdWWzt-jMQBf7cZeoCzDKHG72ysTTCfRoBVc1_SrQTHcHDiiBeW9nCazMLkltyP5NeawR_RNlg'
}
test.each(Object.entries(externalTokens))('Verify external tokens: %s', async (algorithm, token) => {
const key = secrets[algorithm]
let privateKey = key
let publicKey = key
if (typeof key === 'object') {
privateKey = key.private
publicKey = key.public
}
const verified = await JWT.verify(token, publicKey, { algorithm })
expect(verified).toBeTruthy()
const { payload } = JWT.decode(token)
expect({
sub: payload.sub,
name: payload.name
}).toMatchObject({
sub: testPayload.sub,
name: testPayload.name
})
})

8083
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
{
"name": "@tsndr/cloudflare-worker-jwt",
"version": "1.0.8",
"version": "1.4.0",
"description": "A lightweight JWT implementation with ZERO dependencies for Cloudflare Worker",
"main": "index.js",
"scripts": {
"lint": "tslint index.js"
"test": "jest"
},
"repository": {
"type": "git",
@@ -24,6 +24,6 @@
},
"homepage": "https://github.com/tsndr/cloudflare-worker-jwt#readme",
"devDependencies": {
"tslint": "^6.1.3"
"jest": "^28.1.0"
}
}

View File

@@ -1,53 +0,0 @@
{
"jsRules": {
"arrow-return-shorthand": [true, "multiline"],
"binary-expression-operand-order": true,
"class-name": true,
"comment-format": [true, "check-space"],
"curly": [true, "as-needed"],
"encoding": true,
"increment-decrement": [true, "allow-post"],
"indent": [true, "spaces", 2],
"linebreak-style": [true, "LF"],
"no-async-without-await": true,
"no-consecutive-blank-lines": [true, 2],
"no-duplicate-switch-case": true,
"no-duplicate-variable": [true, "check-parameters"],
"no-empty": true,
"no-eval": true,
"no-invalid-template-strings": true,
"no-invalid-this": true,
"no-irregular-whitespace": true,
"no-return-await": true,
"no-shadowed-variable": true,
"no-sparse-arrays": true,
"no-string-throw": true,
"no-tautology-expression": true,
"no-this-assignment": [true, {"allowed-names": ["^self$"], "allow-destructuring": true}],
"no-trailing-whitespace": true,
"no-unnecessary-callback-wrapper": true,
"no-unnecessary-initializer": true,
"no-unsafe-finally": true,
"no-var-keyword": true,
"object-literal-key-quotes": [true, "consistent-as-needed"],
"one-line": [true, "check-catch", "check-finally", "check-else", "check-open-brace", "check-whitespace"],
"one-variable-per-declaration": [true, "ignore-for-loop"],
"ordered-imports": true,
"prefer-conditional-expression": [true, "check-else-if"],
"prefer-const": true,
"prefer-object-spread": true,
"prefer-switch": [true, {"min-cases": 3}],
"prefer-template": [true, "allow-single-concat"],
"prefer-while": true,
"quotemark": [true, "single"],
"semicolon": [true, "never"],
"space-before-function-paren": [true, "never"],
"static-this": true,
"trailing-comma": [true, {"multiline": "never", "singleline": "never"}],
"triple-equals": true,
"unnecessary-constructor": [true, {"check-super-calls": true}],
"unnecessary-else": [true, {"allow-else-if": true}],
"use-isnan": true,
"whitespace": [true, "check-branch", "check-decl", "check-operator", "check-module", "check-separator", "check-rest-spread", "check-type", "check-typecast", "check-type-operator", "check-preblock", "check-postbrace"]
}
}