@arcblock/jwt 1.18.166 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  }