Skip to content

Commit b570ca1

Browse files
foufrixacolytec3
andauthored
internalization of jwt-simple (#3458)
* migration of jwt-simple to /ext * add test jwt-simple test currently passing * solve linting for in * encode ok - decode nok * fix building issue * update licence ref * remove version property * update export to fit ES6 and add base case for encode/decode argument resulting to TS errors * Simplify API and use base64url from scure * update scure/base to latest version --------- Co-authored-by: acolytec3 <[email protected]>
1 parent c225537 commit b570ca1

File tree

12 files changed

+380
-44
lines changed

12 files changed

+380
-44
lines changed

package-lock.json

Lines changed: 6 additions & 32 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/client/archive/libp2p/net/package.json.browser.deps

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@
8080
"fs-extra": "^10.1.0",
8181
"it-pipe": "^1.1.0",
8282
"jayson": "^4.0.0",
83-
"jwt-simple": "^0.5.6",
8483
"level": "^8.0.0",
8584
"memory-level": "^1.0.0",
8685
"peer-id": "^0.14.3",
@@ -133,7 +132,6 @@
133132
"@types/body-parser": "^1.19.2",
134133
"@types/connect": "^3.4.35",
135134
"@types/fs-extra": "^9.0.13",
136-
"@types/jwt-simple": "^0.5.33",
137135
"@types/yargs": "^17.0.24",
138136
"constants-browserify": "^1.0.0",
139137
"crypto-browserify": "^3.12.0",

packages/client/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"@multiformats/multiaddr": "^12.2.1",
7373
"@polkadot/util": "^12.6.2",
7474
"@polkadot/wasm-crypto": "^7.3.2",
75+
"@scure/base": "^1.1.7",
7576
"abstract-level": "^1.0.3",
7677
"body-parser": "^1.19.2",
7778
"chalk": "^4.1.2",
@@ -82,7 +83,6 @@
8283
"it-pipe": "^1.1.0",
8384
"jayson": "^4.0.0",
8485
"js-sdsl": "^4.4.0",
85-
"jwt-simple": "^0.5.6",
8686
"kzg-wasm": "^0.4.0",
8787
"level": "^8.0.0",
8888
"memory-level": "^1.0.0",
@@ -96,7 +96,6 @@
9696
"@types/connect": "^3.4.35",
9797
"@types/cors": "^2.8.17",
9898
"@types/fs-extra": "^11.0.4",
99-
"@types/jwt-simple": "^0.5.33",
10099
"@types/ws": "^8.5.10",
101100
"@types/yargs": "^17.0.24",
102101
"eventsource": "^2.0.2",
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
/**
2+
* Ported to Typescript from original implementation below:
3+
* https://github.com/hokaccha/node-jwt-simple -- MIT licensed
4+
*/
5+
6+
/**
7+
* module dependencies
8+
*/
9+
import { bytesToUtf8, utf8ToBytes } from '@ethereumjs/util'
10+
import { base64url } from '@scure/base'
11+
import crypto from 'crypto'
12+
13+
/**
14+
* support algorithm mapping
15+
*/
16+
export type TAlgorithm = 'HS256' | 'HS384' | 'HS512' | 'RS256'
17+
const algorithmMap: Record<string, string> = {
18+
HS256: 'sha256',
19+
HS384: 'sha384',
20+
HS512: 'sha512',
21+
RS256: 'RSA-SHA256',
22+
}
23+
24+
/**
25+
* Map algorithm to hmac or sign type, to determine which crypto function to use
26+
*/
27+
const typeMap: Record<string, string> = {
28+
HS256: 'hmac',
29+
HS384: 'hmac',
30+
HS512: 'hmac',
31+
RS256: 'sign',
32+
}
33+
34+
/**
35+
* expose object
36+
*/
37+
38+
/**
39+
* private util functions
40+
*/
41+
42+
function assignProperties(dest: any, source: any) {
43+
for (const [attr] of Object.entries(source)) {
44+
if (Object.prototype.hasOwnProperty.call(source, attr)) {
45+
dest[attr] = source[attr]
46+
}
47+
}
48+
}
49+
50+
function assertAlgorithm(alg: any): asserts alg is Algorithm {
51+
if (!['HS256', 'HS384', 'HS512', 'RS256'].includes(alg)) {
52+
throw new Error('Algorithm not supported')
53+
}
54+
}
55+
56+
function base64urlUnescape(str: string) {
57+
str += new Array(5 - (str.length % 4)).join('=')
58+
return str.replace(/-/g, '+').replace(/_/g, '/')
59+
}
60+
61+
function base64urlEscape(str: string) {
62+
return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
63+
}
64+
65+
function sign(input: any, key: string, method: string, type: string) {
66+
let base64str
67+
if (type === 'hmac') {
68+
base64str = crypto.createHmac(method, key).update(input).digest('base64')
69+
} else if (type === 'sign') {
70+
base64str = crypto.createSign(method).update(input).sign(key, 'base64')
71+
} else {
72+
throw new Error('Algorithm type not recognized')
73+
}
74+
75+
return base64urlEscape(base64str)
76+
}
77+
78+
function verify(input: any, key: string, method: string, type: string, signature: string) {
79+
if (type === 'hmac') {
80+
return signature === sign(input, key, method, type)
81+
} else if (type === 'sign') {
82+
return crypto
83+
.createVerify(method)
84+
.update(input)
85+
.verify(key, base64urlUnescape(signature), 'base64')
86+
} else {
87+
throw new Error('Algorithm type not recognized')
88+
}
89+
}
90+
91+
/**
92+
* Decode jwt
93+
*
94+
* @param {Object} token
95+
* @param {String} key
96+
* @param {Boolean} [noVerify]
97+
* @param {String} [algorithm]
98+
* @return {Object} payload
99+
* @api public
100+
*/
101+
const decode = function jwt_decode(
102+
token: string,
103+
key: string,
104+
noVerify: boolean = false,
105+
algorithm: string = ''
106+
) {
107+
// check token
108+
if (!token) {
109+
throw new Error('No token supplied')
110+
}
111+
// check segments
112+
const segments = token.split('.')
113+
if (segments.length !== 3) {
114+
throw new Error('Not enough or too many segments')
115+
}
116+
117+
// All segment should be base64
118+
const headerSeg = segments[0]
119+
const payloadSeg = segments[1]
120+
const signatureSeg = segments[2]
121+
122+
// base64 decode and parse JSON
123+
const header = JSON.parse(bytesToUtf8(base64url.decode(headerSeg)))
124+
const payload = JSON.parse(bytesToUtf8(base64url.decode(payloadSeg)))
125+
126+
if (!noVerify) {
127+
if (!algorithm && /BEGIN( RSA)? PUBLIC KEY/.test(key.toString())) {
128+
algorithm = 'RS256'
129+
}
130+
131+
algorithm = algorithm || header.alg
132+
133+
assertAlgorithm(algorithm)
134+
const signingMethod = algorithmMap[algorithm]
135+
const signingType = typeMap[algorithm]
136+
137+
// verify signature. `sign` will return base64 string.
138+
const signingInput = [headerSeg, payloadSeg].join('.')
139+
if (verify(signingInput, key, signingMethod, signingType, signatureSeg) === false) {
140+
throw new Error('Signature verification failed')
141+
}
142+
143+
// Support for nbf and exp claims.
144+
// According to the RFC, they should be in seconds.
145+
if (payload.nbf !== undefined && Date.now() < payload.nbf * 1000) {
146+
throw new Error('Token not yet active')
147+
}
148+
149+
if (payload.exp !== undefined && Date.now() > payload.exp * 1000) {
150+
throw new Error('Token expired')
151+
}
152+
}
153+
154+
return payload
155+
}
156+
157+
/**
158+
* Encode jwt
159+
*
160+
* @param {Object} payload
161+
* @param {String} key
162+
* @param {String} algorithm
163+
* @param {Object} options
164+
* @return {String} token
165+
* @api public
166+
*/
167+
const encode = function jwt_encode(
168+
payload: any,
169+
key: string,
170+
algorithm: string = '',
171+
options: any = undefined
172+
) {
173+
// Check key
174+
if (!key) {
175+
throw new Error('Require key')
176+
}
177+
178+
// Check algorithm, default is HS256
179+
if (!algorithm) {
180+
algorithm = 'HS256'
181+
}
182+
183+
assertAlgorithm(algorithm)
184+
const signingMethod = algorithmMap[algorithm]
185+
const signingType = typeMap[algorithm]
186+
187+
// header, typ is fixed value.
188+
const header = { typ: 'JWT', alg: algorithm }
189+
if (options !== undefined && options.header !== undefined) {
190+
assignProperties(header, options.header)
191+
}
192+
193+
// create segments, all segments should be base64 string
194+
const segments = []
195+
segments.push(base64url.encode(utf8ToBytes(JSON.stringify(header))))
196+
segments.push(base64url.encode(utf8ToBytes(JSON.stringify(payload))))
197+
segments.push(sign(segments.join('.'), key, signingMethod, signingType))
198+
199+
return segments.join('.')
200+
}
201+
202+
export const jwt = { encode, decode }

packages/client/src/util/rpc.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import Connect from 'connect'
33
import cors from 'cors'
44
import { createServer } from 'http'
55
import jayson from 'jayson/promise/index.js'
6-
import jwt from 'jwt-simple'
76
import { inspect } from 'util'
87

8+
import { jwt } from '../ext/jwt-simple.js'
9+
10+
import type { TAlgorithm } from '../ext/jwt-simple.js'
911
import type { Logger } from '../logging.js'
1012
import type { RPCManager } from '../rpc/index.js'
1113
import type { IncomingMessage } from 'connect'
1214
import type { HttpServer } from 'jayson/promise'
13-
import type { TAlgorithm } from 'jwt-simple'
1415
const { json: jsonParser } = bodyParser
1516
const { decode } = jwt
1617

0 commit comments

Comments
 (0)