@arcblock/jwt 1.18.166 → 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 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 declare type JwtBody = {
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 declare type JwtHeader = {
10
+ export type JwtHeader = {
11
11
  alg: string;
12
12
  type: 'JWT';
13
13
  };
14
- export declare type JwtToken = {
14
+ export type JwtToken = {
15
15
  header: JwtHeader;
16
16
  body: JwtBody;
17
17
  signature: string;
18
18
  };
19
- export declare type JwtVerifyOptions = Partial<{
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.verify = exports.decode = exports.signV2 = exports.sign = void 0;
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
- function verify(token, signerPk, options) {
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.18.166",
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.18.166",
22
- "@ocap/mcrypto": "1.18.166",
23
- "@ocap/util": "1.18.166",
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.2.3",
30
- "@types/jest": "^29.5.12",
31
- "@types/json-stable-stringify": "^1.0.34",
32
- "@types/node": "^17.0.45",
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.25.0",
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": "^4.8.4"
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
- "typings": "lib/index.d.ts",
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": "58c8356b3b8c238728560e4c3fef6ed1704d3ac4"
78
+ "gitHead": "1b6fac03988fb18507c8ef4c21de282762005f87"
68
79
  }