@arcblock/jwt 1.18.165 → 1.19.0
Sign up to get free protection for your applications and to get access to all the features.
- package/esm/index.d.ts +58 -0
- package/esm/index.js +185 -0
- package/lib/index.d.ts +5 -5
- package/lib/index.js +15 -10
- package/package.json +27 -16
package/esm/index.d.ts
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
import { BytesType } from '@ocap/util';
|
2
|
+
export type JwtBody = {
|
3
|
+
iss: string;
|
4
|
+
iat: string;
|
5
|
+
nbf: string;
|
6
|
+
exp: string;
|
7
|
+
version: string;
|
8
|
+
[key: string]: any;
|
9
|
+
};
|
10
|
+
export type JwtHeader = {
|
11
|
+
alg: string;
|
12
|
+
type: 'JWT';
|
13
|
+
};
|
14
|
+
export type JwtToken = {
|
15
|
+
header: JwtHeader;
|
16
|
+
body: JwtBody;
|
17
|
+
signature: string;
|
18
|
+
};
|
19
|
+
export type JwtVerifyOptions = Partial<{
|
20
|
+
tolerance: number;
|
21
|
+
enforceTimestamp: boolean;
|
22
|
+
signerKey: string;
|
23
|
+
}>;
|
24
|
+
/**
|
25
|
+
*
|
26
|
+
*
|
27
|
+
* @param {string} signer - address string
|
28
|
+
* @param {string} sk - hex encoded secret key
|
29
|
+
* @param {*} [payload={}] - data to be included before signing
|
30
|
+
* @param {boolean} [doSign=true] - do we need to sign the payload or just return the content to be signed
|
31
|
+
* @param {string} [version='1.0.0']
|
32
|
+
* @return {*} {string} - hex encoded signature
|
33
|
+
*/
|
34
|
+
export declare function sign(signer: string, sk?: BytesType, payload?: {}, doSign?: boolean, version?: string): string;
|
35
|
+
export declare function signV2(signer: string, sk?: BytesType, payload?: any): string;
|
36
|
+
export declare function decode(token: string, bodyOnly?: true): JwtBody;
|
37
|
+
export declare function decode(token: string, bodyOnly?: false): JwtToken;
|
38
|
+
/**
|
39
|
+
* Verify a jwt token
|
40
|
+
*
|
41
|
+
* @param {string} token - the jwt token
|
42
|
+
* @param {string} signerPk - signer public key
|
43
|
+
* @param {{
|
44
|
+
* tolerance: number; - number of seconds to tolerant expire
|
45
|
+
* enforceTimestamp: boolean; - whether should be verify timestamps?
|
46
|
+
* signerKey: string; - which field should be used to pick the signer
|
47
|
+
* }} [{
|
48
|
+
* tolerance,
|
49
|
+
* enforceTimestamp,
|
50
|
+
* signerKey,
|
51
|
+
* }={
|
52
|
+
* tolerance: 5,
|
53
|
+
* enforceTimestamp: true,
|
54
|
+
* signerKey: 'iss',
|
55
|
+
* }]
|
56
|
+
* @return {*} {boolean}
|
57
|
+
*/
|
58
|
+
export declare function verify(token: string, signerPk: BytesType, options?: JwtVerifyOptions): Promise<boolean>;
|
package/esm/index.js
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
2
|
+
import stringify from 'json-stable-stringify';
|
3
|
+
import semver from 'semver';
|
4
|
+
import { toHex, toBase64, fromBase64 } from '@ocap/util';
|
5
|
+
import { toDid, toStrictHex, toTypeInfo, isValid, isFromPublicKey } from '@arcblock/did';
|
6
|
+
import { types, getSigner, Hasher } from '@ocap/mcrypto';
|
7
|
+
import Debug from 'debug';
|
8
|
+
const debug = Debug('@arcblock/jwt');
|
9
|
+
// we start hashing before signing after 1.1
|
10
|
+
const JWT_VERSION_REQUIRE_HASH_BEFORE_SIGN = '1.1.0';
|
11
|
+
// since ios requires a fixed length of input to sign, we use sha3 here before sign
|
12
|
+
const hasher = Hasher.SHA3.hash256;
|
13
|
+
/**
|
14
|
+
*
|
15
|
+
*
|
16
|
+
* @param {string} signer - address string
|
17
|
+
* @param {string} sk - hex encoded secret key
|
18
|
+
* @param {*} [payload={}] - data to be included before signing
|
19
|
+
* @param {boolean} [doSign=true] - do we need to sign the payload or just return the content to be signed
|
20
|
+
* @param {string} [version='1.0.0']
|
21
|
+
* @return {*} {string} - hex encoded signature
|
22
|
+
*/
|
23
|
+
export function sign(signer, sk, payload = {}, doSign = true, version = '1.0.0') {
|
24
|
+
if (isValid(signer) === false) {
|
25
|
+
throw new Error('Cannot do sign with invalid signer');
|
26
|
+
}
|
27
|
+
const type = toTypeInfo(signer);
|
28
|
+
const headers = {
|
29
|
+
[types.KeyType.SECP256K1]: {
|
30
|
+
alg: 'ES256K',
|
31
|
+
type: 'JWT',
|
32
|
+
},
|
33
|
+
[types.KeyType.ED25519]: {
|
34
|
+
alg: 'Ed25519',
|
35
|
+
type: 'JWT',
|
36
|
+
},
|
37
|
+
[types.KeyType.ETHEREUM]: {
|
38
|
+
alg: 'Ethereum',
|
39
|
+
type: 'JWT',
|
40
|
+
},
|
41
|
+
[types.KeyType.PASSKEY]: {
|
42
|
+
alg: 'Passkey',
|
43
|
+
type: 'JWT',
|
44
|
+
},
|
45
|
+
};
|
46
|
+
// make header
|
47
|
+
const header = headers[type.pk];
|
48
|
+
const headerB64 = toBase64(stringify(header));
|
49
|
+
// make body
|
50
|
+
const now = Math.floor(Date.now() / 1000);
|
51
|
+
const body = {
|
52
|
+
iss: toDid(signer),
|
53
|
+
iat: String(now),
|
54
|
+
nbf: String(now),
|
55
|
+
exp: String(now + 5 * 60),
|
56
|
+
version,
|
57
|
+
...(payload || {}),
|
58
|
+
};
|
59
|
+
// remove empty keys
|
60
|
+
Object.keys(body).forEach((x) => {
|
61
|
+
if (typeof body[x] === 'undefined' || body[x] == null || body[x] === '') {
|
62
|
+
delete body[x];
|
63
|
+
}
|
64
|
+
});
|
65
|
+
const bodyB64 = toBase64(stringify(body));
|
66
|
+
debug('sign.body', body);
|
67
|
+
// @ts-ignore make signature
|
68
|
+
const msgHex = toHex(`${headerB64}.${bodyB64}`);
|
69
|
+
const msgHash = semver.gte(semver.coerce(version).version, JWT_VERSION_REQUIRE_HASH_BEFORE_SIGN)
|
70
|
+
? hasher(msgHex)
|
71
|
+
: msgHex;
|
72
|
+
// istanbul ignore if
|
73
|
+
if (!doSign) {
|
74
|
+
return `${headerB64}.${bodyB64}`;
|
75
|
+
}
|
76
|
+
const sigHex = getSigner(type.pk).sign(msgHash, sk);
|
77
|
+
const sigB64 = toBase64(sigHex);
|
78
|
+
return [headerB64, bodyB64, sigB64].join('.');
|
79
|
+
}
|
80
|
+
export function signV2(signer, sk, payload = {}) {
|
81
|
+
return sign(signer, sk, payload, !!sk, '1.1.0');
|
82
|
+
}
|
83
|
+
export function decode(token, bodyOnly = true) {
|
84
|
+
const [headerB64, bodyB64, sigB64] = token.split('.');
|
85
|
+
const header = JSON.parse(fromBase64(headerB64).toString());
|
86
|
+
const body = JSON.parse(fromBase64(bodyB64).toString());
|
87
|
+
const sig = Buffer.from(fromBase64(sigB64)).toString('hex');
|
88
|
+
if (bodyOnly) {
|
89
|
+
return body;
|
90
|
+
}
|
91
|
+
return { header, body, signature: `0x${toStrictHex(sig)}` };
|
92
|
+
}
|
93
|
+
/**
|
94
|
+
* Verify a jwt token
|
95
|
+
*
|
96
|
+
* @param {string} token - the jwt token
|
97
|
+
* @param {string} signerPk - signer public key
|
98
|
+
* @param {{
|
99
|
+
* tolerance: number; - number of seconds to tolerant expire
|
100
|
+
* enforceTimestamp: boolean; - whether should be verify timestamps?
|
101
|
+
* signerKey: string; - which field should be used to pick the signer
|
102
|
+
* }} [{
|
103
|
+
* tolerance,
|
104
|
+
* enforceTimestamp,
|
105
|
+
* signerKey,
|
106
|
+
* }={
|
107
|
+
* tolerance: 5,
|
108
|
+
* enforceTimestamp: true,
|
109
|
+
* signerKey: 'iss',
|
110
|
+
* }]
|
111
|
+
* @return {*} {boolean}
|
112
|
+
*/
|
113
|
+
// eslint-disable-next-line require-await
|
114
|
+
export async function verify(token, signerPk, options) {
|
115
|
+
const { tolerance, enforceTimestamp, signerKey } = Object.assign({
|
116
|
+
tolerance: 5,
|
117
|
+
enforceTimestamp: true,
|
118
|
+
signerKey: 'iss',
|
119
|
+
}, options);
|
120
|
+
try {
|
121
|
+
const [headerB64, bodyB64] = token.split('.');
|
122
|
+
const { header, body, signature } = decode(token, false);
|
123
|
+
if (!signature) {
|
124
|
+
debug('verify.error.emptySig');
|
125
|
+
return false;
|
126
|
+
}
|
127
|
+
if (!header.alg) {
|
128
|
+
debug('verify.error.emptyAlg');
|
129
|
+
return false;
|
130
|
+
}
|
131
|
+
const signerDid = body[signerKey];
|
132
|
+
if (!signerDid) {
|
133
|
+
debug('verify.error.emptySignerDid');
|
134
|
+
return false;
|
135
|
+
}
|
136
|
+
if (isFromPublicKey(signerDid, signerPk) === false) {
|
137
|
+
debug('verify.error.signerDidAndPkNotMatch');
|
138
|
+
return false;
|
139
|
+
}
|
140
|
+
if (enforceTimestamp) {
|
141
|
+
const now = Math.ceil(Date.now() / 1000);
|
142
|
+
const exp = Number(body.exp) || 0;
|
143
|
+
const iat = Number(body.iat) || 0;
|
144
|
+
const nbf = Number(body.nbf) || 0;
|
145
|
+
debug('verify.enforceTimestamp', { now, exp, iat, nbf });
|
146
|
+
if (exp && exp + tolerance < now) {
|
147
|
+
debug('verify.error.expired');
|
148
|
+
return false;
|
149
|
+
}
|
150
|
+
if (iat && iat > now && iat - now > tolerance) {
|
151
|
+
debug('verify.error.issuedAt');
|
152
|
+
return false;
|
153
|
+
}
|
154
|
+
if (nbf && nbf > now && nbf - now > tolerance) {
|
155
|
+
debug('verify.error.notBefore');
|
156
|
+
return false;
|
157
|
+
}
|
158
|
+
}
|
159
|
+
const signers = {
|
160
|
+
secp256k1: getSigner(types.KeyType.SECP256K1),
|
161
|
+
es256k: getSigner(types.KeyType.SECP256K1),
|
162
|
+
ed25519: getSigner(types.KeyType.ED25519),
|
163
|
+
ethereum: getSigner(types.KeyType.ETHEREUM),
|
164
|
+
passkey: getSigner(types.KeyType.PASSKEY),
|
165
|
+
};
|
166
|
+
const alg = header.alg.toLowerCase();
|
167
|
+
if (signers[alg]) {
|
168
|
+
// @ts-ignore
|
169
|
+
const msgHex = toHex(`${headerB64}.${bodyB64}`);
|
170
|
+
// If we are using v1.1 protocol, the message should be hashed before verify
|
171
|
+
const version = body.version && semver.coerce(body.version) ? semver.coerce(body.version).version : '';
|
172
|
+
if (version && version === JWT_VERSION_REQUIRE_HASH_BEFORE_SIGN) {
|
173
|
+
return signers[alg].verify(hasher(msgHex), signature, signerPk);
|
174
|
+
}
|
175
|
+
return signers[alg].verify(msgHex, signature, signerPk);
|
176
|
+
}
|
177
|
+
debug('verify.error.crypto');
|
178
|
+
return false;
|
179
|
+
}
|
180
|
+
catch (err) {
|
181
|
+
debug('verify.error.exception');
|
182
|
+
debug(err);
|
183
|
+
return false;
|
184
|
+
}
|
185
|
+
}
|
package/lib/index.d.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { BytesType } from '@ocap/util';
|
2
|
-
export
|
2
|
+
export type JwtBody = {
|
3
3
|
iss: string;
|
4
4
|
iat: string;
|
5
5
|
nbf: string;
|
@@ -7,16 +7,16 @@ export declare type JwtBody = {
|
|
7
7
|
version: string;
|
8
8
|
[key: string]: any;
|
9
9
|
};
|
10
|
-
export
|
10
|
+
export type JwtHeader = {
|
11
11
|
alg: string;
|
12
12
|
type: 'JWT';
|
13
13
|
};
|
14
|
-
export
|
14
|
+
export type JwtToken = {
|
15
15
|
header: JwtHeader;
|
16
16
|
body: JwtBody;
|
17
17
|
signature: string;
|
18
18
|
};
|
19
|
-
export
|
19
|
+
export type JwtVerifyOptions = Partial<{
|
20
20
|
tolerance: number;
|
21
21
|
enforceTimestamp: boolean;
|
22
22
|
signerKey: string;
|
@@ -55,4 +55,4 @@ export declare function decode(token: string, bodyOnly?: false): JwtToken;
|
|
55
55
|
* }]
|
56
56
|
* @return {*} {boolean}
|
57
57
|
*/
|
58
|
-
export declare function verify(token: string, signerPk: BytesType, options?: JwtVerifyOptions): boolean
|
58
|
+
export declare function verify(token: string, signerPk: BytesType, options?: JwtVerifyOptions): Promise<boolean>;
|
package/lib/index.js
CHANGED
@@ -3,7 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.
|
6
|
+
exports.sign = sign;
|
7
|
+
exports.signV2 = signV2;
|
8
|
+
exports.decode = decode;
|
9
|
+
exports.verify = verify;
|
7
10
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
8
11
|
const json_stable_stringify_1 = __importDefault(require("json-stable-stringify"));
|
9
12
|
const semver_1 = __importDefault(require("semver"));
|
@@ -44,6 +47,10 @@ function sign(signer, sk, payload = {}, doSign = true, version = '1.0.0') {
|
|
44
47
|
alg: 'Ethereum',
|
45
48
|
type: 'JWT',
|
46
49
|
},
|
50
|
+
[mcrypto_1.types.KeyType.PASSKEY]: {
|
51
|
+
alg: 'Passkey',
|
52
|
+
type: 'JWT',
|
53
|
+
},
|
47
54
|
};
|
48
55
|
// make header
|
49
56
|
const header = headers[type.pk];
|
@@ -66,24 +73,22 @@ function sign(signer, sk, payload = {}, doSign = true, version = '1.0.0') {
|
|
66
73
|
});
|
67
74
|
const bodyB64 = (0, util_1.toBase64)((0, json_stable_stringify_1.default)(body));
|
68
75
|
debug('sign.body', body);
|
69
|
-
// istanbul ignore if
|
70
|
-
if (!doSign) {
|
71
|
-
return `${headerB64}.${bodyB64}`;
|
72
|
-
}
|
73
76
|
// @ts-ignore make signature
|
74
77
|
const msgHex = (0, util_1.toHex)(`${headerB64}.${bodyB64}`);
|
75
78
|
const msgHash = semver_1.default.gte(semver_1.default.coerce(version).version, JWT_VERSION_REQUIRE_HASH_BEFORE_SIGN)
|
76
79
|
? hasher(msgHex)
|
77
80
|
: msgHex;
|
81
|
+
// istanbul ignore if
|
82
|
+
if (!doSign) {
|
83
|
+
return `${headerB64}.${bodyB64}`;
|
84
|
+
}
|
78
85
|
const sigHex = (0, mcrypto_1.getSigner)(type.pk).sign(msgHash, sk);
|
79
86
|
const sigB64 = (0, util_1.toBase64)(sigHex);
|
80
87
|
return [headerB64, bodyB64, sigB64].join('.');
|
81
88
|
}
|
82
|
-
exports.sign = sign;
|
83
89
|
function signV2(signer, sk, payload = {}) {
|
84
90
|
return sign(signer, sk, payload, !!sk, '1.1.0');
|
85
91
|
}
|
86
|
-
exports.signV2 = signV2;
|
87
92
|
function decode(token, bodyOnly = true) {
|
88
93
|
const [headerB64, bodyB64, sigB64] = token.split('.');
|
89
94
|
const header = JSON.parse((0, util_1.fromBase64)(headerB64).toString());
|
@@ -94,7 +99,6 @@ function decode(token, bodyOnly = true) {
|
|
94
99
|
}
|
95
100
|
return { header, body, signature: `0x${(0, did_1.toStrictHex)(sig)}` };
|
96
101
|
}
|
97
|
-
exports.decode = decode;
|
98
102
|
/**
|
99
103
|
* Verify a jwt token
|
100
104
|
*
|
@@ -115,7 +119,8 @@ exports.decode = decode;
|
|
115
119
|
* }]
|
116
120
|
* @return {*} {boolean}
|
117
121
|
*/
|
118
|
-
|
122
|
+
// eslint-disable-next-line require-await
|
123
|
+
async function verify(token, signerPk, options) {
|
119
124
|
const { tolerance, enforceTimestamp, signerKey } = Object.assign({
|
120
125
|
tolerance: 5,
|
121
126
|
enforceTimestamp: true,
|
@@ -165,6 +170,7 @@ function verify(token, signerPk, options) {
|
|
165
170
|
es256k: (0, mcrypto_1.getSigner)(mcrypto_1.types.KeyType.SECP256K1),
|
166
171
|
ed25519: (0, mcrypto_1.getSigner)(mcrypto_1.types.KeyType.ED25519),
|
167
172
|
ethereum: (0, mcrypto_1.getSigner)(mcrypto_1.types.KeyType.ETHEREUM),
|
173
|
+
passkey: (0, mcrypto_1.getSigner)(mcrypto_1.types.KeyType.PASSKEY),
|
168
174
|
};
|
169
175
|
const alg = header.alg.toLowerCase();
|
170
176
|
if (signers[alg]) {
|
@@ -186,4 +192,3 @@ function verify(token, signerPk, options) {
|
|
186
192
|
return false;
|
187
193
|
}
|
188
194
|
}
|
189
|
-
exports.verify = verify;
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@arcblock/jwt",
|
3
3
|
"description": "JSON Web Token variant for arcblock DID solutions",
|
4
|
-
"version": "1.
|
4
|
+
"version": "1.19.0",
|
5
5
|
"author": {
|
6
6
|
"name": "wangshijun",
|
7
7
|
"email": "shijun@arcblock.io",
|
@@ -18,24 +18,24 @@
|
|
18
18
|
"access": "public"
|
19
19
|
},
|
20
20
|
"dependencies": {
|
21
|
-
"@arcblock/did": "1.
|
22
|
-
"@ocap/mcrypto": "1.
|
23
|
-
"@ocap/util": "1.
|
21
|
+
"@arcblock/did": "1.19.0",
|
22
|
+
"@ocap/mcrypto": "1.19.0",
|
23
|
+
"@ocap/util": "1.19.0",
|
24
24
|
"debug": "^4.3.6",
|
25
25
|
"json-stable-stringify": "^1.0.1",
|
26
26
|
"semver": "^7.6.3"
|
27
27
|
},
|
28
28
|
"devDependencies": {
|
29
|
-
"@arcblock/eslint-config-ts": "0.
|
30
|
-
"@types/jest": "^29.5.
|
31
|
-
"@types/json-stable-stringify": "^1.0.
|
32
|
-
"@types/node": "^
|
29
|
+
"@arcblock/eslint-config-ts": "0.3.3",
|
30
|
+
"@types/jest": "^29.5.13",
|
31
|
+
"@types/json-stable-stringify": "^1.0.36",
|
32
|
+
"@types/node": "^22.7.5",
|
33
33
|
"@types/semver": "^7.5.8",
|
34
|
-
"eslint": "^8.
|
34
|
+
"eslint": "^8.57.0",
|
35
35
|
"jest": "^29.7.0",
|
36
36
|
"ts-jest": "^29.2.5",
|
37
37
|
"tslib": "^2.4.0",
|
38
|
-
"typescript": "^
|
38
|
+
"typescript": "^5.6.2"
|
39
39
|
},
|
40
40
|
"homepage": "https://github.com/ArcBlock/blockchain/tree/master/did/jwt",
|
41
41
|
"keywords": [
|
@@ -45,10 +45,19 @@
|
|
45
45
|
"nodejs"
|
46
46
|
],
|
47
47
|
"license": "Apache-2.0",
|
48
|
-
"main": "lib/index.js",
|
49
|
-
"
|
48
|
+
"main": "./lib/index.js",
|
49
|
+
"module": "./lib/index.js",
|
50
|
+
"types": "./esm/index.d.ts",
|
51
|
+
"exports": {
|
52
|
+
".": {
|
53
|
+
"import": "./esm/index.js",
|
54
|
+
"require": "./lib/index.js",
|
55
|
+
"default": "./esm/index.js"
|
56
|
+
}
|
57
|
+
},
|
50
58
|
"files": [
|
51
|
-
"lib"
|
59
|
+
"lib",
|
60
|
+
"esm"
|
52
61
|
],
|
53
62
|
"repository": {
|
54
63
|
"type": "git",
|
@@ -59,10 +68,12 @@
|
|
59
68
|
"lint:fix": "eslint src tests --fix",
|
60
69
|
"test": "jest --forceExit --detectOpenHandles",
|
61
70
|
"coverage": "npm run test -- --coverage",
|
62
|
-
"clean": "rm -fr lib",
|
71
|
+
"clean": "rm -fr lib esm",
|
63
72
|
"prebuild": "npm run clean",
|
64
73
|
"build:watch": "npm run build -- -w",
|
65
|
-
"build": "tsc"
|
74
|
+
"build:cjs": "tsc -p tsconfig.cjs.json",
|
75
|
+
"build:esm": "tsc -p tsconfig.esm.json",
|
76
|
+
"build": "npm run build:cjs && npm run build:esm"
|
66
77
|
},
|
67
|
-
"gitHead": "
|
78
|
+
"gitHead": "1b6fac03988fb18507c8ef4c21de282762005f87"
|
68
79
|
}
|