@arcblock/jwt
Advanced tools
Comparing version 1.16.16 to 1.16.17
@@ -1,16 +0,67 @@ | ||
// Generate by [js2dts@0.3.3](https://github.com/whxaxes/js2dts#readme) | ||
declare const _Lib: _Lib.T102; | ||
declare namespace _Lib { | ||
export interface T101 { | ||
tolerance?: number; | ||
enforceTimestamp?: boolean; | ||
signerKey?: string; | ||
} | ||
export interface T102 { | ||
sign: (signer: string, sk: string, payload?: any, doSign?: boolean, version?: string) => string; | ||
verify: (token: string, signerPk: string, T100?: _Lib.T101) => boolean; | ||
decode: (token: string, payloadOnly?: boolean) => any; | ||
} | ||
} | ||
export = _Lib; | ||
import { BytesType } from '@ocap/util'; | ||
export declare type JwtBody = { | ||
iss?: string; | ||
iat?: string; | ||
nbf?: string; | ||
exp?: string; | ||
version?: string; | ||
[key: string]: any; | ||
}; | ||
export declare type JwtHeader = { | ||
alg: string; | ||
type: 'JWT'; | ||
}; | ||
export declare type JwtToken = { | ||
header: JwtHeader; | ||
body: JwtBody; | ||
signature: string; | ||
}; | ||
export declare type JwtVerifyOptions = Partial<{ | ||
tolerance: number; | ||
enforceTimestamp: boolean; | ||
signerKey: string; | ||
}>; | ||
/** | ||
* | ||
* | ||
* @param {string} signer - address string | ||
* @param {string} sk - hex encoded secret key | ||
* @param {*} [payload={}] - data to be included before signing | ||
* @param {boolean} [doSign=true] - do we need to sign the payload or just return the content to be signed | ||
* @param {string} [version='1.0.0'] | ||
* @return {*} {string} - hex encoded signature | ||
*/ | ||
export declare function sign(signer: string, sk: string, payload?: {}, doSign?: boolean, version?: string): string; | ||
/** | ||
* | ||
* | ||
* @param {string} token - jwt string | ||
* @param {boolean} [payloadOnly=true] | ||
* @return {*} {{ | ||
* header: any; | ||
* body: any; | ||
* signature: string; | ||
* }} | ||
*/ | ||
export declare function decode(token: string, payloadOnly?: boolean): JwtToken; | ||
/** | ||
* | ||
* | ||
* @param {string} token - the jwt token | ||
* @param {string} signerPk - signer public key | ||
* @param {{ | ||
* tolerance: number; - number of seconds to tolerant expire | ||
* enforceTimestamp: boolean; - whether should be verify timestamps? | ||
* signerKey: string; - which field should be used to pick the signer | ||
* }} [{ | ||
* tolerance, | ||
* enforceTimestamp, | ||
* signerKey, | ||
* }={ | ||
* tolerance: 5, | ||
* enforceTimestamp: true, | ||
* signerKey: 'iss', | ||
* }] | ||
* @return {*} {boolean} | ||
*/ | ||
export declare function verify(token: string, signerPk: BytesType, options?: JwtVerifyOptions): boolean; |
357
lib/index.js
@@ -1,206 +0,195 @@ | ||
const semver = require('semver'); | ||
const stringify = require('json-stable-stringify'); | ||
const Mcrypto = require('@ocap/mcrypto'); | ||
const { toHex, toBase64, fromBase64 } = require('@ocap/util'); | ||
const { toDid, toStrictHex, toTypeInfo, isValid, isFromPublicKey } = require('@arcblock/did'); | ||
// eslint-disable-next-line | ||
const debug = require('debug')(require('../package.json').name); | ||
const { types, getSigner } = Mcrypto; | ||
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.verify = exports.decode = exports.sign = void 0; | ||
/* eslint-disable @typescript-eslint/ban-ts-comment */ | ||
const json_stable_stringify_1 = __importDefault(require("json-stable-stringify")); | ||
const semver_1 = __importDefault(require("semver")); | ||
const util_1 = require("@ocap/util"); | ||
const did_1 = require("@arcblock/did"); | ||
const mcrypto_1 = require("@ocap/mcrypto"); | ||
const debug_1 = __importDefault(require("debug")); | ||
const debug = (0, debug_1.default)('@arcblock/jwt'); | ||
// we start hashing before signing after 1.1 | ||
const JWT_VERSION_REQUIRE_HASH_BEFORE_SIGN = '1.1.0'; | ||
// since ios requires a fixed length of input to sign, we use sha3 here before sign | ||
const hasher = Mcrypto.Hasher.SHA3.hash256; | ||
const hasher = mcrypto_1.Hasher.SHA3.hash256; | ||
/** | ||
* Generate and sign a jwt token | ||
* | ||
* @public | ||
* @static | ||
* | ||
* @param {string} signer - address string | ||
* @param {string} sk - hex encoded secret key | ||
* @param {object} [payload={}] - data to be included before signing | ||
* @param {*} [payload={}] - data to be included before signing | ||
* @param {boolean} [doSign=true] - do we need to sign the payload or just return the content to be signed | ||
* @returns {string} hex encoded signature | ||
* @param {string} [version='1.0.0'] | ||
* @return {*} {string} - hex encoded signature | ||
*/ | ||
const sign = (signer, sk, payload = {}, doSign = true, version = '1.0.0') => { | ||
if (isValid(signer) === false) { | ||
throw new Error('Cannot do sign with invalid signer'); | ||
} | ||
const type = toTypeInfo(signer); | ||
const headers = { | ||
[types.KeyType.SECP256K1]: { | ||
alg: 'ES256K', | ||
type: 'JWT', | ||
}, | ||
[types.KeyType.ED25519]: { | ||
alg: 'Ed25519', | ||
type: 'JWT', | ||
}, | ||
[types.KeyType.ETHEREUM]: { | ||
alg: 'Ethereum', | ||
type: 'JWT', | ||
}, | ||
}; | ||
// make header | ||
const header = headers[type.pk]; | ||
const headerB64 = toBase64(stringify(header)); | ||
// make body | ||
const now = Math.floor(Date.now() / 1000); | ||
let body = { | ||
iss: toDid(signer), | ||
iat: String(now), | ||
nbf: String(now), | ||
exp: String(now + 5 * 60), | ||
version, | ||
...(payload || {}), | ||
}; | ||
// remove empty keys | ||
body = Object.keys(body) | ||
.filter((x) => { | ||
if (typeof body[x] === 'undefined' || body[x] == null || body[x] === '') { | ||
return false; | ||
} | ||
return true; | ||
function sign(signer, sk, payload = {}, doSign = true, version = '1.0.0') { | ||
if ((0, did_1.isValid)(signer) === false) { | ||
throw new Error('Cannot do sign with invalid signer'); | ||
} | ||
const type = (0, did_1.toTypeInfo)(signer); | ||
const headers = { | ||
[mcrypto_1.types.KeyType.SECP256K1]: { | ||
alg: 'ES256K', | ||
type: 'JWT', | ||
}, | ||
[mcrypto_1.types.KeyType.ED25519]: { | ||
alg: 'Ed25519', | ||
type: 'JWT', | ||
}, | ||
[mcrypto_1.types.KeyType.ETHEREUM]: { | ||
alg: 'Ethereum', | ||
type: 'JWT', | ||
}, | ||
}; | ||
// make header | ||
const header = headers[type.pk]; | ||
const headerB64 = (0, util_1.toBase64)((0, json_stable_stringify_1.default)(header)); | ||
// make body | ||
const now = Math.floor(Date.now() / 1000); | ||
let body = Object.assign({ iss: (0, did_1.toDid)(signer), iat: String(now), nbf: String(now), exp: String(now + 5 * 60), version }, (payload || {})); | ||
// remove empty keys | ||
body = Object.keys(body) | ||
.filter((x) => { | ||
if (typeof body[x] === 'undefined' || body[x] == null || body[x] === '') { | ||
return false; | ||
} | ||
return true; | ||
}) | ||
.reduce((acc, x) => { | ||
acc[x] = body[x]; | ||
return acc; | ||
.reduce((acc, x) => { | ||
acc[x] = body[x]; | ||
return acc; | ||
}, {}); | ||
const bodyB64 = toBase64(stringify(body)); | ||
debug('sign.body', body); | ||
// istanbul ignore if | ||
if (!doSign) { | ||
return `${headerB64}.${bodyB64}`; | ||
} | ||
// make signature | ||
const msgHex = toHex(`${headerB64}.${bodyB64}`); | ||
const msgHash = semver.gte(semver.coerce(version).version, JWT_VERSION_REQUIRE_HASH_BEFORE_SIGN) | ||
? hasher(msgHex) | ||
: msgHex; | ||
const sigHex = getSigner(type.pk).sign(msgHash, sk); | ||
const sigB64 = toBase64(sigHex); | ||
return [headerB64, bodyB64, sigB64].join('.'); | ||
}; | ||
const bodyB64 = (0, util_1.toBase64)((0, json_stable_stringify_1.default)(body)); | ||
debug('sign.body', body); | ||
// istanbul ignore if | ||
if (!doSign) { | ||
return `${headerB64}.${bodyB64}`; | ||
} | ||
// @ts-ignore make signature | ||
const msgHex = (0, util_1.toHex)(`${headerB64}.${bodyB64}`); | ||
const msgHash = semver_1.default.gte(semver_1.default.coerce(version).version, JWT_VERSION_REQUIRE_HASH_BEFORE_SIGN) | ||
? hasher(msgHex) | ||
: msgHex; | ||
const sigHex = (0, mcrypto_1.getSigner)(type.pk).sign(msgHash, sk); | ||
const sigB64 = (0, util_1.toBase64)(sigHex); | ||
return [headerB64, bodyB64, sigB64].join('.'); | ||
} | ||
exports.sign = sign; | ||
/** | ||
* Decode info from jwt token | ||
* | ||
* @public | ||
* @static | ||
* | ||
* @param {string} token - jwt string | ||
* @param {boolean} [payloadOnly=false] | ||
* @returns {object} | ||
* @param {boolean} [payloadOnly=true] | ||
* @return {*} {{ | ||
* header: any; | ||
* body: any; | ||
* signature: string; | ||
* }} | ||
*/ | ||
const decode = (token, payloadOnly = true) => { | ||
const [headerB64, bodyB64, sigB64] = token.split('.'); | ||
const header = JSON.parse(fromBase64(headerB64)); | ||
const body = JSON.parse(fromBase64(bodyB64)); | ||
const sig = Buffer.from(fromBase64(sigB64)).toString('hex'); | ||
if (payloadOnly) { | ||
return body; | ||
} | ||
return { header, body, signature: `0x${toStrictHex(sig)}` }; | ||
}; | ||
function decode(token, payloadOnly = true) { | ||
const [headerB64, bodyB64, sigB64] = token.split('.'); | ||
const header = JSON.parse((0, util_1.fromBase64)(headerB64).toString()); | ||
const body = JSON.parse((0, util_1.fromBase64)(bodyB64).toString()); | ||
const sig = Buffer.from((0, util_1.fromBase64)(sigB64)).toString('hex'); | ||
if (payloadOnly) { | ||
return body; | ||
} | ||
return { header, body, signature: `0x${(0, did_1.toStrictHex)(sig)}` }; | ||
} | ||
exports.decode = decode; | ||
/** | ||
* Verify a jwt token signed with signerPk and signerDid | ||
* | ||
* @public | ||
* @static | ||
* @param {string} token - the jwt token | ||
* | ||
* @param {string} token - the jwt token | ||
* @param {string} signerPk - signer public key | ||
* @param {object} options - options to customize the verify | ||
* @param {number} [options.tolerance=5] - number of seconds to tolerant expire | ||
* @param {boolean} [options.enforceTimestamp=true] - whether should be verify timestamps? | ||
* @param {string} [options.signerKey='iss'] - which field should be used to pick the signer | ||
* @returns {boolean} | ||
* @param {{ | ||
* tolerance: number; - number of seconds to tolerant expire | ||
* enforceTimestamp: boolean; - whether should be verify timestamps? | ||
* signerKey: string; - which field should be used to pick the signer | ||
* }} [{ | ||
* tolerance, | ||
* enforceTimestamp, | ||
* signerKey, | ||
* }={ | ||
* tolerance: 5, | ||
* enforceTimestamp: true, | ||
* signerKey: 'iss', | ||
* }] | ||
* @return {*} {boolean} | ||
*/ | ||
const verify = (token, signerPk, { tolerance = 5, enforceTimestamp = true, signerKey = 'iss' } = {}) => { | ||
try { | ||
const [headerB64, bodyB64] = token.split('.'); | ||
const { header, body, signature } = decode(token, false); | ||
if (!signature) { | ||
debug('verify.error.emptySig'); | ||
return false; | ||
function verify(token, signerPk, options) { | ||
const { tolerance, enforceTimestamp, signerKey } = Object.assign({ | ||
tolerance: 5, | ||
enforceTimestamp: true, | ||
signerKey: 'iss', | ||
}, options); | ||
try { | ||
const [headerB64, bodyB64] = token.split('.'); | ||
const { header, body, signature } = decode(token, false); | ||
if (!signature) { | ||
debug('verify.error.emptySig'); | ||
return false; | ||
} | ||
if (!header.alg) { | ||
debug('verify.error.emptyAlg'); | ||
return false; | ||
} | ||
const signerDid = body[signerKey]; | ||
if (!signerDid) { | ||
debug('verify.error.emptySignerDid'); | ||
return false; | ||
} | ||
if ((0, did_1.isFromPublicKey)(signerDid, signerPk) === false) { | ||
debug('verify.error.signerDidAndPkNotMatch'); | ||
return false; | ||
} | ||
if (enforceTimestamp) { | ||
const now = Math.ceil(Date.now() / 1000); | ||
const exp = Number(body.exp) || 0; | ||
const iat = Number(body.iat) || 0; | ||
const nbf = Number(body.nbf) || 0; | ||
debug('verify.enforceTimestamp', { now, exp, iat, nbf }); | ||
if (exp && exp + tolerance < now) { | ||
debug('verify.error.expired'); | ||
return false; | ||
} | ||
if (iat && iat > now && iat - now > tolerance) { | ||
debug('verify.error.issuedAt'); | ||
return false; | ||
} | ||
if (nbf && nbf > now && nbf - now > tolerance) { | ||
debug('verify.error.notBefore'); | ||
return false; | ||
} | ||
} | ||
const signers = { | ||
secp256k1: (0, mcrypto_1.getSigner)(mcrypto_1.types.KeyType.SECP256K1), | ||
es256k: (0, mcrypto_1.getSigner)(mcrypto_1.types.KeyType.SECP256K1), | ||
ed25519: (0, mcrypto_1.getSigner)(mcrypto_1.types.KeyType.ED25519), | ||
ethereum: (0, mcrypto_1.getSigner)(mcrypto_1.types.KeyType.ETHEREUM), | ||
}; | ||
const alg = header.alg.toLowerCase(); | ||
if (signers[alg]) { | ||
// @ts-ignore | ||
const msgHex = (0, util_1.toHex)(`${headerB64}.${bodyB64}`); | ||
// If we are using v1.1 protocol, the message should be hashed before verify | ||
const version = body.version && semver_1.default.coerce(body.version) ? semver_1.default.coerce(body.version).version : ''; | ||
if (version && version === JWT_VERSION_REQUIRE_HASH_BEFORE_SIGN) { | ||
return signers[alg].verify(hasher(msgHex), signature, signerPk); | ||
} | ||
return signers[alg].verify(msgHex, signature, signerPk); | ||
} | ||
debug('verify.error.crypto'); | ||
return false; | ||
} | ||
if (!header.alg) { | ||
debug('verify.error.emptyAlg'); | ||
return false; | ||
} | ||
const signerDid = body[signerKey]; | ||
if (!signerDid) { | ||
debug('verify.error.emptySignerDid'); | ||
return false; | ||
} | ||
if (isFromPublicKey(signerDid, signerPk) === false) { | ||
debug('verify.error.signerDidAndPkNotMatch'); | ||
return false; | ||
} | ||
if (enforceTimestamp) { | ||
const now = Math.ceil(Date.now() / 1000); | ||
const exp = Number(body.exp) || 0; | ||
const iat = Number(body.iat) || 0; | ||
const nbf = Number(body.nbf) || 0; | ||
debug('verify.enforceTimestamp', { now, exp, iat, nbf }); | ||
if (exp && exp + tolerance < now) { | ||
debug('verify.error.expired'); | ||
catch (err) { | ||
debug('verify.error.exception'); | ||
debug(err); | ||
return false; | ||
} | ||
if (iat && iat > now && iat - now > tolerance) { | ||
debug('verify.error.issuedAt'); | ||
return false; | ||
} | ||
if (nbf && nbf > now && nbf - now > tolerance) { | ||
debug('verify.error.notBefore'); | ||
return false; | ||
} | ||
} | ||
const signers = { | ||
secp256k1: getSigner(types.KeyType.SECP256K1), | ||
es256k: getSigner(types.KeyType.SECP256K1), | ||
ed25519: getSigner(types.KeyType.ED25519), | ||
ethereum: getSigner(types.KeyType.ETHEREUM), | ||
}; | ||
const alg = header.alg.toLowerCase(); | ||
if (signers[alg]) { | ||
const msgHex = toHex(`${headerB64}.${bodyB64}`); | ||
// If we are using v1.1 protocol, the message should be hashed before verify | ||
const version = body.version && semver.coerce(body.version) ? semver.coerce(body.version).version : ''; | ||
if (version && version === JWT_VERSION_REQUIRE_HASH_BEFORE_SIGN) { | ||
return signers[alg].verify(hasher(msgHex), signature, signerPk); | ||
} | ||
return signers[alg].verify(msgHex, signature, signerPk); | ||
} | ||
debug('verify.error.crypto'); | ||
return false; | ||
} catch (err) { | ||
debug('verify.error.exception'); | ||
debug(err); | ||
return false; | ||
} | ||
}; | ||
module.exports = { | ||
sign, | ||
verify, | ||
decode, | ||
}; | ||
} | ||
exports.verify = verify; |
{ | ||
"name": "@arcblock/jwt", | ||
"description": "JSON Web Token variant for arcblock DID solutions", | ||
"version": "1.16.16", | ||
"version": "1.16.17", | ||
"author": { | ||
@@ -21,5 +21,5 @@ "name": "wangshijun", | ||
"dependencies": { | ||
"@arcblock/did": "1.16.16", | ||
"@ocap/mcrypto": "1.16.16", | ||
"@ocap/util": "1.16.16", | ||
"@arcblock/did": "1.16.17", | ||
"@ocap/mcrypto": "1.16.17", | ||
"@ocap/util": "1.16.17", | ||
"debug": "^4.3.3", | ||
@@ -30,3 +30,12 @@ "json-stable-stringify": "^1.0.1", | ||
"devDependencies": { | ||
"jest": "^27.3.1" | ||
"@arcblock/eslint-config-ts": "0.2.2", | ||
"@types/jest": "^27.5.1", | ||
"@types/json-stable-stringify": "^1.0.34", | ||
"@types/node": "^17.0.33", | ||
"@types/semver": "^7.3.9", | ||
"eslint": "^8.17.0", | ||
"jest": "^27.3.1", | ||
"ts-jest": "^28.0.0", | ||
"tslib": "^2.4.0", | ||
"typescript": "^4.6.4" | ||
}, | ||
@@ -41,3 +50,4 @@ "homepage": "https://github.com/ArcBlock/asset-chain/tree/master/did/jwt", | ||
"license": "Apache-2.0", | ||
"main": "./lib/index.js", | ||
"main": "lib/index.js", | ||
"typings": "lib/index.d.ts", | ||
"files": [ | ||
@@ -51,8 +61,12 @@ "lib" | ||
"scripts": { | ||
"lint": "eslint lib tests", | ||
"lint:fix": "eslint --fix lib tests", | ||
"lint": "eslint src tests", | ||
"lint:fix": "eslint src tests --fix", | ||
"test": "jest --forceExit --detectOpenHandles", | ||
"coverage": "yarn test -- --coverage" | ||
"coverage": "yarn test -- --coverage", | ||
"clean": "rm -fr lib", | ||
"prebuild": "npm run clean", | ||
"build:watch": "npm run build -- -w", | ||
"build": "tsc" | ||
}, | ||
"gitHead": "051f9620995cf24407374cc4811b0fa6ed6dc7ca" | ||
"gitHead": "489ce5e03bce27ddcd535390228b11ab56e7a2e3" | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
16162
262
10
1
+ Added@arcblock/did@1.16.17(transitive)
+ Added@ocap/mcrypto@1.16.17(transitive)
+ Added@ocap/util@1.16.17(transitive)
- Removed@arcblock/did@1.16.16(transitive)
- Removed@ocap/mcrypto@1.16.16(transitive)
- Removed@ocap/util@1.16.16(transitive)
- Removedbn.js@5.2.0(transitive)
Updated@arcblock/did@1.16.17
Updated@ocap/mcrypto@1.16.17
Updated@ocap/util@1.16.17