diff --git a/package-lock.json b/package-lock.json index 2e1bbb3b..cfdb617d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1318,6 +1318,15 @@ "babel-plugin-jest-hoist": "^24.6.0" } }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, "babylon": { "version": "7.0.0-beta.19", "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.19.tgz", @@ -1385,15 +1394,6 @@ } } }, - "base-x": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.5.tgz", - "integrity": "sha512-C3picSgzPSLE+jW3tcBzJoGwitOtazb5B+5YmAxZm2ybmTi9LNgAtDO/jjVEBZwHoXmDBZ9m/IELj3elJVRBcA==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, "base64-js": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", @@ -2382,8 +2382,7 @@ "core-js": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", - "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==", - "dev": true + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" }, "core-util-is": { "version": "1.0.2", @@ -3057,9 +3056,9 @@ "dev": true }, "did-resolver": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/did-resolver/-/did-resolver-0.0.6.tgz", - "integrity": "sha512-PqxzaoomTbJG3IzEouUGgppu3xrsbGKHS75zS3vS/Hfm56XxLpwIe7yFLokgXUbMWmLa0dczFHOibmebO4wRLA==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/did-resolver/-/did-resolver-1.0.0.tgz", + "integrity": "sha512-mgJG0oqlkG7jfRzW0yN9qKawp24M4thGFdfIaZI30SAJXhpkkjqbkRxqMZLJNwqXEM0cqFbXaiFDqnd9Q1UUaw==" }, "diff": { "version": "3.5.0", @@ -3705,6 +3704,153 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, + "ethjs-abi": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.2.1.tgz", + "integrity": "sha1-4KepOn6BFjqUR3utVu3lJKtt5TM=", + "requires": { + "bn.js": "4.11.6", + "js-sha3": "0.5.5", + "number-to-bn": "1.7.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" + }, + "js-sha3": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.5.tgz", + "integrity": "sha1-uvDA6MVK1ZA0R9+Wreekobynmko=" + } + } + }, + "ethjs-contract": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/ethjs-contract/-/ethjs-contract-0.1.9.tgz", + "integrity": "sha1-HCdmiWpW1H7B1tZhgpxJzDilUgo=", + "requires": { + "ethjs-abi": "0.2.0", + "ethjs-filter": "0.1.5", + "ethjs-util": "0.1.3", + "js-sha3": "0.5.5" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" + }, + "ethjs-abi": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.2.0.tgz", + "integrity": "sha1-0+LCIQEVIPxJm3FoIDbBT8wvWyU=", + "requires": { + "bn.js": "4.11.6", + "js-sha3": "0.5.5", + "number-to-bn": "1.7.0" + } + }, + "js-sha3": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.5.tgz", + "integrity": "sha1-uvDA6MVK1ZA0R9+Wreekobynmko=" + } + } + }, + "ethjs-filter": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/ethjs-filter/-/ethjs-filter-0.1.5.tgz", + "integrity": "sha1-ARKvYBfCRnfjK4/esg5hlgGbdZg=" + }, + "ethjs-format": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/ethjs-format/-/ethjs-format-0.2.7.tgz", + "integrity": "sha512-uNYAi+r3/mvR3xYu2AfSXx5teP4ovy9z2FrRsblU+h2logsaIKZPi9V3bn3V7wuRcnG0HZ3QydgZuVaRo06C4Q==", + "requires": { + "bn.js": "4.11.6", + "ethjs-schema": "0.2.1", + "ethjs-util": "0.1.3", + "is-hex-prefixed": "1.0.0", + "number-to-bn": "1.7.0", + "strip-hex-prefix": "1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" + } + } + }, + "ethjs-provider-http": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-provider-http/-/ethjs-provider-http-0.1.6.tgz", + "integrity": "sha1-HsXZtL4lfvHValALIqdBmF6IlCA=", + "requires": { + "xhr2": "0.1.3" + } + }, + "ethjs-query": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.3.8.tgz", + "integrity": "sha512-/J5JydqrOzU8O7VBOwZKUWXxHDGr46VqNjBCJgBVNNda+tv7Xc8Y2uJc6aMHHVbeN3YOQ7YRElgIc0q1CI02lQ==", + "requires": { + "babel-runtime": "^6.26.0", + "ethjs-format": "0.2.7", + "ethjs-rpc": "0.2.0", + "promise-to-callback": "^1.0.0" + } + }, + "ethjs-rpc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ethjs-rpc/-/ethjs-rpc-0.2.0.tgz", + "integrity": "sha512-RINulkNZTKnj4R/cjYYtYMnFFaBcVALzbtEJEONrrka8IeoarNB9Jbzn+2rT00Cv8y/CxAI+GgY1d0/i2iQeOg==", + "requires": { + "promise-to-callback": "^1.0.0" + } + }, + "ethjs-schema": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ethjs-schema/-/ethjs-schema-0.2.1.tgz", + "integrity": "sha512-DXd8lwNrhT9sjsh/Vd2Z+4pfyGxhc0POVnLBUfwk5udtdoBzADyq+sK39dcb48+ZU+2VgtwHxtGWnLnCfmfW5g==" + }, + "ethjs-util": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.3.tgz", + "integrity": "sha1-39XqSkANxeQhqInK9H4IGtp4u1U=", + "requires": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" + } + }, + "ethr-did-registry": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/ethr-did-registry/-/ethr-did-registry-0.0.3.tgz", + "integrity": "sha512-4BPvMGkxAK9vTduCq6D5b8ZqjteD2cvDIPPriXP6nnmPhWKFSxypo+AFvyQ0omJGa0cGTR+dkdI/8jiF7U/qaw==" + }, + "ethr-did-resolver": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ethr-did-resolver/-/ethr-did-resolver-1.0.0.tgz", + "integrity": "sha512-Aoqhut32K43SVtpD8xLxA/hpwJFu5Msq+tXN4oYTQ1mb0Z6FJLG2D4lvtbW2MYnEvqnF90XovYe9uz4RV8emKw==", + "requires": { + "buffer": "^5.1.0", + "did-resolver": "1.0.0", + "ethjs-abi": "^0.2.1", + "ethjs-contract": "^0.1.9", + "ethjs-provider-http": "^0.1.6", + "ethjs-query": "^0.3.5", + "ethr-did-registry": "^0.0.3" + }, + "dependencies": { + "did-resolver": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/did-resolver/-/did-resolver-1.0.0.tgz", + "integrity": "sha512-mgJG0oqlkG7jfRzW0yN9qKawp24M4thGFdfIaZI30SAJXhpkkjqbkRxqMZLJNwqXEM0cqFbXaiFDqnd9Q1UUaw==" + } + } + }, "events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", @@ -5241,6 +5387,22 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "https-did-resolver": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-did-resolver/-/https-did-resolver-1.0.0.tgz", + "integrity": "sha512-JdD4s17hwW8XdPf5L0yiSsuNBdBFSqxoP9hCZkxykrIANiXeBiAb2AwyiahhxiOiUdr5vU56djGMq2Yd1ehNrQ==", + "requires": { + "did-resolver": "1.0.0", + "xmlhttprequest": "^1.8.0" + }, + "dependencies": { + "did-resolver": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/did-resolver/-/did-resolver-1.0.0.tgz", + "integrity": "sha512-mgJG0oqlkG7jfRzW0yN9qKawp24M4thGFdfIaZI30SAJXhpkkjqbkRxqMZLJNwqXEM0cqFbXaiFDqnd9Q1UUaw==" + } + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -5595,6 +5757,11 @@ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", "dev": true }, + "is-fn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fn/-/is-fn-1.0.0.tgz", + "integrity": "sha1-lUPV3nvPWwiiLsiiC65uKG1RDYw=" + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -5616,6 +5783,11 @@ "is-extglob": "^1.0.0" } }, + "is-hex-prefixed": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", + "integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=" + }, "is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", @@ -7207,6 +7379,14 @@ "ed2curve": "git+https://github.com/uport-project/ed2curve-js.git#0eed8c601a5ad48e8bec1c61c335acac5428f51c", "tweetnacl": "^1.0.0", "tweetnacl-util": "^0.15.0" + }, + "dependencies": { + "did-resolver": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/did-resolver/-/did-resolver-0.0.6.tgz", + "integrity": "sha512-PqxzaoomTbJG3IzEouUGgppu3xrsbGKHS75zS3vS/Hfm56XxLpwIe7yFLokgXUbMWmLa0dczFHOibmebO4wRLA==", + "dev": true + } } }, "nan": { @@ -7456,6 +7636,22 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, + "number-to-bn": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", + "integrity": "sha1-uzYjWS9+X54AMLGXe9QaDFP+HqA=", + "requires": { + "bn.js": "4.11.6", + "strip-hex-prefix": "1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" + } + } + }, "nwsapi": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz", @@ -9211,6 +9407,15 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, + "promise-to-callback": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/promise-to-callback/-/promise-to-callback-1.0.0.tgz", + "integrity": "sha1-XSp0kBC/tn2WNZj805YHRqaP7vc=", + "requires": { + "is-fn": "^1.0.0", + "set-immediate-shim": "^1.0.1" + } + }, "promise.series": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", @@ -9583,8 +9788,7 @@ "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" }, "regex-cache": { "version": "0.4.4", @@ -11620,6 +11824,11 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", @@ -12576,6 +12785,14 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, + "strip-hex-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", + "integrity": "sha1-DF8VX+8RUTczd96du1iNoFUA428=", + "requires": { + "is-hex-prefixed": "1.0.0" + } + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -13383,26 +13600,6 @@ "buffer": "^5.2.1" } }, - "uport-did-resolver": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/uport-did-resolver/-/uport-did-resolver-0.0.7.tgz", - "integrity": "sha512-zntpAR9kXN1EQFXJeZk8P4VoDKQ26AwGwhTWUUbbU6VoX5UAJzbYrLaXBtQhRn2CFxMLHsvwXgcCZpKHui9sqg==", - "dev": true, - "requires": { - "did-resolver": "0.0.6", - "uport-lite": "^1.0.5" - } - }, - "uport-lite": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/uport-lite/-/uport-lite-1.0.5.tgz", - "integrity": "sha512-qj2COq8Qf+4ooDX3GeJlEzHJAmopxwOy19xRvRY6G7AN8kmMeGSDaQQ+9tYhFwcr6tVhVNjRyopeW8vbc7FCFg==", - "dev": true, - "requires": { - "base-x": "^3.0.0", - "xmlhttprequest": "^1.8.0" - } - }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -13866,6 +14063,11 @@ "async-limiter": "~1.0.0" } }, + "xhr2": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.1.3.tgz", + "integrity": "sha1-y/xHWaabSoiOeM9PILBRA4dXvRE=" + }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", @@ -13881,8 +14083,7 @@ "xmlhttprequest": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", - "dev": true + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" }, "xtend": { "version": "4.0.1", diff --git a/package.json b/package.json index 936bb007..14a35659 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "tslint-config-prettier": "^1.18.0", "tslint-eslint-rules": "^5.4.0", "typescript": "^3.4.5", - "uport-did-resolver": "0.0.7", "webpack": "^4.30.0", "webpack-cli": "^3.3.1" }, @@ -83,8 +82,10 @@ "@babel/runtime": "^7.3.1", "@stablelib/utf8": "^0.10.1", "buffer": "^5.2.1", - "did-resolver": "0.0.6", + "did-resolver": "^1.0.0", "elliptic": "^6.4.0", + "ethr-did-resolver": "^1.0.1", + "https-did-resolver": "^1.0.0", "js-sha256": "^0.9.0", "js-sha3": "^0.8.0", "tweetnacl": "^1.0.1", diff --git a/src/JWT.ts b/src/JWT.ts index 677f24f9..5b273b45 100644 --- a/src/JWT.ts +++ b/src/JWT.ts @@ -1,7 +1,9 @@ import VerifierAlgorithm from './VerifierAlgorithm' import SignerAlgorithm from './SignerAlgorithm' import base64url from 'uport-base64url' -import resolve, { DIDDocument, PublicKey } from 'did-resolver' +import { Resolver, DIDDocument, PublicKey } from 'did-resolver' +import { getResolver as getEthrDidResolver } from 'ethr-did-resolver' +import getHttpsDidResolver from 'https-did-resolver' export interface EcdsaSignature { r: string @@ -82,9 +84,13 @@ const SUPPORTED_PUBLIC_KEY_TYPES: PublicKeyTypes = { Ed25519: ['ED25519SignatureVerification'] } -const JOSE_HEADER = { typ: 'JWT' } const defaultAlg = 'ES256K' +export const resolver = new Resolver({ + ...getEthrDidResolver(), + ...getHttpsDidResolver() +}) + function encodeSection(data: any): string { return base64url.encode(JSON.stringify(data)) } @@ -296,7 +302,7 @@ export async function resolveAuthenticator( throw new Error(`No supported signature types for algorithm ${alg}`) } const issuer: string = normalizeDID(mnidOrDid) - const doc: DIDDocument = await resolve(issuer) + const doc: DIDDocument = await resolver.resolve(issuer) if (!doc) throw new Error(`Unable to resolve DID document for ${issuer}`) // is there some way to have authenticationKeys be a single type? const authenticationKeys: boolean | string[] = auth diff --git a/src/__tests__/JWT-test.ts b/src/__tests__/JWT-test.ts index 06a3e17f..a415675f 100644 --- a/src/__tests__/JWT-test.ts +++ b/src/__tests__/JWT-test.ts @@ -3,29 +3,24 @@ import { verifyJWT, decodeJWT, resolveAuthenticator, - NBF_SKEW + NBF_SKEW, + resolver, + normalizeDID } from '../JWT' import { TokenVerifier } from 'jsontokens' -import registerResolver from 'uport-did-resolver' import SimpleSigner from '../SimpleSigner' import NaclSigner from '../NaclSigner' -import { - registerNaclDID, - loadIdentity, - verifyJWT as naclVerifyJWT -} from 'nacl-did' +import { verifyJWT as naclVerifyJWT } from 'nacl-did' import MockDate from 'mockdate' -registerResolver() -registerNaclDID() - +const originalResolve = resolver.resolve const NOW = 1485321133 MockDate.set(NOW * 1000 + 123) -const audMnid = '2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqY' -const aud = `did:uport:${audMnid}` -const mnid = '2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX' -const did = `did:uport:${mnid}` +const audAddress = '0x20c769ec9c0996ba7737a4826c2aaff00b1b2040' +const aud = `did:ethr:${audAddress}` +const address = '0xf3beac30c498d9e26865f34fcaa57dbb935b0d74' +const did = `did:ethr:${address}` const alg = 'ES256K' const privateKey = @@ -54,108 +49,101 @@ const didDoc = { ] } -const ethDidDoc = { - '@context': 'https://w3id.org/did/v1', - id: did, - publicKey: [ - { - id: `${did}#keys-1`, - type: 'Secp256k1VerificationKey2018', - owner: did, - ethereumAddress: '0xf3beac30c498d9e26865f34fcaa57dbb935b0d74' - } - ] -} - describe('createJWT()', () => { describe('ES256K', () => { - it('creates a valid JWT', () => { - return createJWT( + it('creates a valid JWT', async () => { + const jwt = await createJWT( { requested: ['name', 'phone'] }, { issuer: did, signer } - ).then(jwt => { - return expect(verifier.verify(jwt)).toBeTruthy() - }) + ) + return expect(verifier.verify(jwt)).toBeTruthy() }) - it('creates a valid JWT using a MNID', () => { - return createJWT( + it('creates a valid JWT using a MNID', async () => { + const jwt = await createJWT( { requested: ['name', 'phone'] }, - { issuer: mnid, signer } - ).then(jwt => { - return expect(verifier.verify(jwt)).toBeTruthy() - }) + { issuer: address, signer } + ) + return expect(verifier.verify(jwt)).toBeTruthy() }) - it('creates a JWT with correct format', () => { - return createJWT( + it('creates a JWT with correct format', async () => { + const jwt = await createJWT( { requested: ['name', 'phone'] }, { issuer: did, signer } - ).then(jwt => { - return expect(decodeJWT(jwt)).toMatchSnapshot() - }) + ) + return expect(decodeJWT(jwt)).toMatchSnapshot() }) - it('creates a JWT with correct legacy format', () => { - return createJWT( + it('creates a JWT with correct legacy format', async () => { + const jwt = await createJWT( { requested: ['name', 'phone'] }, - { issuer: mnid, signer } - ).then(jwt => { - return expect(decodeJWT(jwt)).toMatchSnapshot() - }) + { issuer: address, signer } + ) + return expect(decodeJWT(jwt)).toMatchSnapshot() }) - it('creates a JWT with expiry in 10000 seconds', () => { - return createJWT( - { requested: ['name', 'phone'], nbf: Math.floor(new Date().getTime() / 1000) }, + it('creates a JWT with expiry in 10000 seconds', async () => { + const jwt = await createJWT( + { + requested: ['name', 'phone'], + nbf: Math.floor(new Date().getTime() / 1000) + }, { issuer: did, signer, expiresIn: 10000 } - ).then(jwt => { - const { payload } = decodeJWT(jwt) - return expect(payload.exp).toEqual(payload.nbf + 10000) - }) + ) + const { payload } = decodeJWT(jwt) + return expect(payload.exp).toEqual(payload.nbf + 10000) }) it('ignores expiresIn if nbf is not set', async () => { - const { payload } = decodeJWT(await createJWT( - { requested: ['name', 'phone'] }, - { issuer: did, signer, expiresIn: 10000 } - )) + const { payload } = decodeJWT( + await createJWT( + { requested: ['name', 'phone'] }, + { issuer: did, signer, expiresIn: 10000 } + ) + ) return expect(payload.exp).toBeUndefined() }) it('sets iat to the current time by default', async () => { const timestamp = Math.floor(Date.now() / 1000) - const { payload } = decodeJWT(await createJWT( - { requested: ['name', 'phone'] }, - { issuer: did, signer } - )) + const { payload } = decodeJWT( + await createJWT( + { requested: ['name', 'phone'] }, + { issuer: did, signer } + ) + ) return expect(payload.iat).toEqual(timestamp) }) it('sets iat to the value passed in payload', async () => { const timestamp = 2000000 - const { payload } = decodeJWT(await createJWT( - { requested: ['name', 'phone'], iat: timestamp }, - { issuer: did, signer } - )) + const { payload } = decodeJWT( + await createJWT( + { requested: ['name', 'phone'], iat: timestamp }, + { issuer: did, signer } + ) + ) return expect(payload.iat).toEqual(timestamp) }) it('does not set iat if value in payload is undefined', async () => { - const { payload } = decodeJWT(await createJWT( - { requested: ['name', 'phone'], iat: undefined }, - { issuer: did, signer } - )) + const { payload } = decodeJWT( + await createJWT( + { requested: ['name', 'phone'], iat: undefined }, + { issuer: did, signer } + ) + ) return expect(payload.iat).toBeUndefined() }) - it('throws an error if unsupported algorithm is passed in', () => { - return createJWT( - { requested: ['name', 'phone'] }, - { issuer: did, signer, alg: 'BADALGO' } - ).catch(error => { - return expect(error.message).toEqual('Unsupported algorithm BADALGO') - }) + it('throws an error if unsupported algorithm is passed in', async () => { + expect( + createJWT( + { requested: ['name', 'phone'] }, + { issuer: did, signer, alg: 'BADALGO' } + ) + ).rejects.toThrow('Unsupported algorithm BADALGO') }) }) @@ -166,297 +154,208 @@ describe('createJWT()', () => { const signer = NaclSigner(ed25519PrivateKey) const alg = 'Ed25519' - it('creates a valid JWT', () => { - return createJWT( + it('creates a valid JWT', async () => { + const jwt = await createJWT( { requested: ['name', 'phone'] }, { alg, issuer: did, signer } - ).then(jwt => { - return expect(naclVerifyJWT(jwt)).toBeTruthy() - }) + ) + return expect(naclVerifyJWT(jwt)).toBeTruthy() }) - it('creates a JWT with correct format', () => { - return createJWT( + it('creates a JWT with correct format', async () => { + const jwt = await createJWT( { requested: ['name', 'phone'] }, { alg, issuer: did, signer } - ).then(jwt => { - return expect(decodeJWT(jwt)).toMatchSnapshot() - }) + ) + return expect(decodeJWT(jwt)).toMatchSnapshot() }) - it('creates a JWT with expiry in 10000 seconds', () => { - return createJWT( - { requested: ['name', 'phone'], nbf: Math.floor(new Date().getTime() / 1000) }, + it('creates a JWT with expiry in 10000 seconds', async () => { + const jwt = await createJWT( + { + requested: ['name', 'phone'], + nbf: Math.floor(new Date().getTime() / 1000) + }, { alg, issuer: did, signer, expiresIn: 10000 } - ).then(jwt => { - const { payload } = decodeJWT(jwt) - return expect(payload.exp).toEqual(payload.nbf + 10000) - }) + ) + const { payload } = decodeJWT(jwt) + return expect(payload.exp).toEqual(payload.nbf + 10000) }) }) }) describe('verifyJWT()', () => { - registerResolver((id, cb) => { - if (mnid === id) cb(null, didDoc) - if (audMnid === id) cb(null, ethDidDoc) + beforeAll(() => { + resolver.resolve = jest.fn().mockReturnValue(didDoc) + }) + afterAll(() => { + resolver.resolve = originalResolve }) - describe('pregenerated JWT', () => { - const incomingJwt = - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6dXBvcnQ6Mm5RdGlRRzZDZ20xR1lUQmFhS0Fncjc2dVk3aVNleFVrcVg' + - 'iLCJpYXQiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXX0.1hyeUGRBb-cgvjD5KKbpVJBF4TfDjYxrI8SWRJ-GyrJrN' + - 'LAxt4MutKMFQyF1k_YkxbVozGJ_4XmgZqNaW4OvCw' - - it('verifies the JWT and return correct payload', () => { - return verifyJWT(incomingJwt).then(({ payload }) => { - return expect(payload).toMatchSnapshot() - }) - }) - - it('verifies the JWT and return correct profile', () => { - return verifyJWT(incomingJwt).then(({ doc }) => { - return expect(doc).toEqual(didDoc) - }) - }) - - it('verifies the JWT and return correct did for the iss', () => { - return verifyJWT(incomingJwt).then(({ issuer }) => { - return expect(issuer).toEqual(did) - }) - }) - - it('verifies the JWT and return correct signer', () => { - return verifyJWT(incomingJwt).then(({ signer }) => - expect(signer).toEqual(didDoc.publicKey[0]) + // tslint:disable-next-line: max-line-length + const incomingJwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsImlzcyI6ImRpZDpldGhyOjB4OTBlNDVkNzViZDEyNDZlMDkyNDg3MjAxODY0N2RiYTk5NmE4ZTdiOSIsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXX0.KIG2zUO8Quf3ucb9jIncZ1CmH0v-fAZlsKvesfsd9x4RzU0qrvinVd9d30DOeZOwdwEdXkET_wuPoOECwU0IKA' + it('verifies the JWT and return correct payload', async () => { + const { payload } = await verifyJWT(incomingJwt) + return expect(payload).toMatchSnapshot() + }) + it('verifies the JWT and return correct profile', async () => { + const { doc } = await verifyJWT(incomingJwt) + return expect(doc).toEqual(didDoc) + }) + it('verifies the JWT and return correct did for the iss', async () => { + const { issuer } = await verifyJWT(incomingJwt) + return expect(issuer).toEqual( + 'did:ethr:0x90e45d75bd1246e0924872018647dba996a8e7b9' ) }) - - it('verifies the JWT requiring authentication and return correct signer', () => { - return verifyJWT(incomingJwt, { auth: true }).then(({ signer }) => - expect(signer).toEqual(didDoc.publicKey[0]) - ) - }) - }) - - describe('nacl-did jwt', () => { - const privateKey = - 'nlXR4aofRVuLqtn9+XVQNlX4s1nVQvp+TOhBBtYls1IG+sHyIkDP/WN+rWZHGIQp+v2pyct+rkM4asF/YRFQdQ==' - const did = 'did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU' - const naclId = loadIdentity({ privateKey, did }) - const payload = { sub: aud, claim: { superChap: true } } - const incomingJwt = naclId.createJWT(payload) - const publicKey = { - id: `${did}#key1`, - type: 'ED25519SignatureVerification', - owner: did, - publicKeyBase64: 'BvrB8iJAz/1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU=' - } - it('verifies the JWT and return correct payload', () => { - return verifyJWT(incomingJwt).then(({ payload }) => { - return expect(payload).toEqual(payload) - }) - }) - - it('verifies the JWT and return correct profile', () => { - return verifyJWT(incomingJwt).then(({ doc }) => { - return expect(doc).toMatchSnapshot() - }) - }) - - it('verifies the JWT and return correct did for the iss', () => { - return verifyJWT(incomingJwt).then(({ issuer }) => { - return expect(issuer).toEqual(naclId.did) - }) - }) - - it('verifies the JWT and return correct signer', () => { - return verifyJWT(incomingJwt).then(({ signer }) => - expect(signer).toEqual(publicKey) - ) + it('verifies the JWT and return correct signer', async () => { + const { signer } = await verifyJWT(incomingJwt) + return expect(signer).toEqual(didDoc.publicKey[0]) }) - - it('verifies the JWT requiring authentication and return correct signer', () => { - return verifyJWT(incomingJwt, { auth: true }).then(({ signer }) => - expect(signer).toEqual(publicKey) - ) + it('verifies the JWT requiring authentication and return correct signer', async () => { + const { signer } = await verifyJWT(incomingJwt, { auth: true }) + return expect(signer).toEqual(didDoc.publicKey[0]) }) }) describe('badJwt', () => { - const badJwt = - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6dXBvcnQ6Mm5RdGlRRzZDZ20xR1lUQmFhS0Fncjc2dVk3aVNleFVrcVg' + - 'iLCJpYXQiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXX0.1hyeUGRBb-cgvjD5KKbpVJBF4TfDjYxrI8SWRJ-GyrJrN' + - 'LAxt4MutKMFQyF1k_YkxbVozGJ_4XmgZqNaW4OvCX' - it('rejects a JWT with bad signature', () => { - return verifyJWT(badJwt) - .catch(error => - expect(error.message).toEqual('Signature invalid for JWT') - ) - .then(p => expect(p).toBeFalsy()) + // tslint:disable-next-line: max-line-length + const badJwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsImlzcyI6ImRpZDpldGhyOjB4MjBjNzY5ZWM5YzA5OTZiYTc3MzdhNDgyNmMyYWFmZjAwYjFiMjA0MCIsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXX0.TTpuw77fUbd_AY3GJcCumd6F6hxnkskMDJYNpJlI2DQi5MKKudXya9NlyM9e8-KFgTLe-WnXgq9EjWLvjpdiXA' + it('rejects a JWT with bad signature', async () => { + expect(verifyJWT(badJwt)).rejects.toThrowError( + /Signature invalid for JWT/ + ) }) }) describe('validFrom timestamp', () => { it('passes when nbf is in the past', async () => { // tslint:disable-next-line: max-line-length - const jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsIm5iZiI6MTQ4NTI2MTEzMywiaXNzIjoiZGlkOnVwb3J0OjJuUXRpUUc2Q2dtMUdZVEJhYUtBZ3I3NnVZN2lTZXhVa3FYIn0.btzVz7fZsoSEDa7JyWo3cYWL63pkWTKTz8OUzepIesfSFeBozUjX2oq1xOJ2OyzuinnLGwtSqY303VoyALrafA' + const jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsIm5iZiI6MTQ4NTI2MTEzMywiaXNzIjoiZGlkOmV0aHI6MHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0.FUasGkOYqGVxQ7S-QQvh4abGO6Dwr961UjjOxtRTyUDnl6q6ElqHqAK-WMDTmOir21pFPKLYZMtLZ4LTLpm3cQ' + // const jwt = await createJWT({nbf: PAST}, {issuer:did, signer}) expect(verifyJWT(jwt)).resolves.not.toThrow() }) it('passes when nbf is in the past and iat is in the future', async () => { // tslint:disable-next-line: max-line-length - const jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzODExMzMsIm5iZiI6MTQ4NTI2MTEzMywiaXNzIjoiZGlkOnVwb3J0OjJuUXRpUUc2Q2dtMUdZVEJhYUtBZ3I3NnVZN2lTZXhVa3FYIn0.ELsPnDC_YTTkT5hxw09UCLSjWVje9mDs1n_mpvlo2Wk5VJONSy-FDAzm5TunzzCeLixU04m6dD4w6Uk3-OVkww' + const jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzODExMzMsIm5iZiI6MTQ4NTI2MTEzMywiaXNzIjoiZGlkOmV0aHI6MHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0.8BPiSG2e6UBn1osnJ6PJYbPjtPMPaCeutTA9OCp-ZzI-QvvwPCVrrWqTu2YELbzUPwDIJCQ8v8N77xCEjIYSmQ' + // const jwt = await createJWT({nbf:PAST,iat:FUTURE},{issuer:did,signer}) expect(verifyJWT(jwt)).resolves.not.toThrow() }) it('fails when nbf is in the future', async () => { // tslint:disable-next-line: max-line-length const jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsIm5iZiI6MTQ4NTM4MTEzMywiaXNzIjoiZGlkOnVwb3J0OjJuUXRpUUc2Q2dtMUdZVEJhYUtBZ3I3NnVZN2lTZXhVa3FYIn0.rcFuhVHtie3Y09pWxBSf1dnjaVh6FFQLHh-83N-uLty3M5ADJ-jVFFkyt_Eupl8Kr735-oPGn_D1Nj9rl4s_Kw' + // const jwt = await createJWT({nbf:FUTURE},{issuer:did,signer}) expect(verifyJWT(jwt)).rejects.toThrow() }) it('fails when nbf is in the future and iat is in the past', async () => { // tslint:disable-next-line: max-line-length - const jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUyNjExMzMsIm5iZiI6MTQ4NTM4MTEzMywiaXNzIjoiZGlkOnVwb3J0OjJuUXRpUUc2Q2dtMUdZVEJhYUtBZ3I3NnVZN2lTZXhVa3FYIn0.jiVI11IcKNOvnDrJBzojKtNAGaZbEcafcqW-wfP78g6-6RucjYPBi5qvKje35IOvITWvvpXpK48IW-17Srh02w' + const jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUyNjExMzMsIm5iZiI6MTQ4NTM4MTEzMywiaXNzIjoiZGlkOmV0aHI6MHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0.JjEn_huxI9SsBY_3PlD0ShpXvrRgUGFDKAgxJBc1Q5GToVpUTw007-o9BTt7JNi_G2XWmcu2aXXnDn0QFsRIrg' + // const jwt = await createJWT({nbf:FUTURE,iat:PAST},{issuer:did,signer}) expect(verifyJWT(jwt)).rejects.toThrow() }) it('passes when nbf is missing and iat is in the past', async () => { // tslint:disable-next-line: max-line-length - const jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUyNjExMzMsImlzcyI6ImRpZDp1cG9ydDoyblF0aVFHNkNnbTFHWVRCYWFLQWdyNzZ1WTdpU2V4VWtxWCJ9.1VwGHDm7f9V-1Fa545uAwF9NfU3RI8yqRFW6XAHOg0FBeM7krC_rEf0PwqbKFO8MiIBELBwUhW_fT4oZsuggUA' + const jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUyNjExMzMsImlzcyI6ImRpZDpldGhyOjB4ZjNiZWFjMzBjNDk4ZDllMjY4NjVmMzRmY2FhNTdkYmI5MzViMGQ3NCJ9.jkzN5kIVtuRU-Fjte8w5r-ttf9OfhdN38oFJd61CWdI5WnvU1dPCvnx1_kdk2D6Xg-uPqp1VXAb7KA2ZECivmg' + // const jwt = await createJWT({iat:PAST},{issuer:did,signer}) expect(verifyJWT(jwt)).resolves.not.toThrow() }) it('fails when nbf is missing and iat is in the future', async () => { // tslint:disable-next-line: max-line-length - const jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzODExMzMsImlzcyI6ImRpZDp1cG9ydDoyblF0aVFHNkNnbTFHWVRCYWFLQWdyNzZ1WTdpU2V4VWtxWCJ9.jU0R8qP3aUX_3DiFt9tIONiq_P5OooFc-ypUwpqK4plGyw6WiI0FTGfZvq7pOarKrjmSojE9Sm_3ETfMpdQckg' + const jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzODExMzMsImlzcyI6ImRpZDpldGhyOjB4ZjNiZWFjMzBjNDk4ZDllMjY4NjVmMzRmY2FhNTdkYmI5MzViMGQ3NCJ9.FJuHvf9Tby7b4I54Cm1nh8CvLg4QH2wt2K0WfyQaLqlr3NKKI5hAdLalgZksI25gLhNrZwQFnC-nzEOs9PI1SQ' + // const jwt = await createJWT({iat:FUTURE},{issuer:did,signer}) expect(verifyJWT(jwt)).rejects.toThrow() }) it('passes when nbf and iat are both missing', async () => { // tslint:disable-next-line: max-line-length - const jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6dXBvcnQ6Mm5RdGlRRzZDZ20xR1lUQmFhS0Fncjc2dVk3aVNleFVrcVgifQ.5kGKU9ljebhTqvfVDu9MH7vGAqRH0GDTbZNGH45YmhUySgBTyI7u-MkkRit72eFvQAqBfzw6wNUbGf9FPC5AtQ' + const jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6ZXRocjoweGYzYmVhYzMwYzQ5OGQ5ZTI2ODY1ZjM0ZmNhYTU3ZGJiOTM1YjBkNzQifQ.KgnwgMMz-QSOtpba2QMGHMWJoLvhp-H4odjjX1QKnqj4-8dkcK12y7rj7Zq24-1d-1ne86aJCdWtx5VJv3rM7w' + // const jwt = await createJWT({iat:undefined},{issuer:did,signer}) expect(verifyJWT(jwt)).resolves.not.toThrow() }) }) - it('handles ES256K-R algorithm', () => { - return createJWT( + it('handles ES256K-R algorithm', async () => { + const jwt = await createJWT( { hello: 'world' }, { issuer: did, signer, alg: 'ES256K-R' } - ).then(jwt => - verifyJWT(jwt).then( - ({ payload }) => expect(payload).toMatchSnapshot(), - error => expect(error).toBeNull() - ) ) + const { payload } = await verifyJWT(jwt) + return expect(payload).toMatchSnapshot() }) - it('handles ES256K-R algorithm with ethereum address', () => { - return createJWT( + it('handles ES256K-R algorithm with ethereum address', async () => { + const jwt = await createJWT( { hello: 'world' }, { issuer: aud, signer, alg: 'ES256K-R' } - ).then(jwt => - verifyJWT(jwt).then( - ({ payload }) => expect(payload).toMatchSnapshot(), - error => expect(error).toBeNull() - ) ) + const { payload } = await verifyJWT(jwt) + return expect(payload).toMatchSnapshot() }) - it('accepts a valid exp', () => { - return createJWT( - { exp: NOW }, - { issuer: did, signer } - ).then(jwt => - verifyJWT(jwt).then(({ payload }) => expect(payload).toBeDefined()) - ) + it('accepts a valid exp', async () => { + const jwt = await createJWT({ exp: NOW }, { issuer: did, signer }) + const { payload } = await verifyJWT(jwt) + return expect(payload).toBeDefined() }) - it('rejects an expired JWT', () => { - return createJWT({ exp: NOW - NBF_SKEW - 1 }, { issuer: did, signer }).then( - jwt => - verifyJWT(jwt) - .catch(error => - expect(error.message).toEqual( - 'JWT has expired: exp: 1485320832 < now: 1485321133' - ) - ) - .then(p => expect(p).toBeFalsy()) - ) - }) - - it('accepts a valid audience', () => { - return createJWT({ aud }, { issuer: did, signer }).then(jwt => - verifyJWT(jwt, { audience: aud }).then(({ payload }) => - expect(payload).toMatchSnapshot() - ) + it('rejects an expired JWT', async () => { + const jwt = await createJWT( + { exp: NOW - NBF_SKEW - 1 }, + { issuer: did, signer } ) + expect(verifyJWT(jwt)) + .rejects + .toThrow(/JWT has expired/) }) - it('accepts a valid MNID audience', () => { - return createJWT({ aud }, { issuer: did, signer }).then(jwt => - verifyJWT(jwt, { audience: audMnid }).then(({ payload }) => - expect(payload).toMatchSnapshot() - ) - ) + it('accepts a valid audience', async () => { + const jwt = await createJWT({ aud }, { issuer: did, signer }) + const { payload } = await verifyJWT(jwt, { audience: aud }) + return expect(payload).toMatchSnapshot() }) - it('accepts a valid audience using callback_url', () => { - return createJWT( + it('accepts a valid audience using callback_url', async () => { + const jwt = await createJWT( { aud: 'http://pututu.uport.me/unique' }, { issuer: did, signer } - ).then(jwt => - verifyJWT(jwt, { callbackUrl: 'http://pututu.uport.me/unique' }).then( - ({ payload }) => expect(payload).toMatchSnapshot() - ) ) + const { payload } = await verifyJWT(jwt, { + callbackUrl: 'http://pututu.uport.me/unique' + }) + return expect(payload).toMatchSnapshot() }) - it('rejects invalid audience', () => { - return createJWT({ aud }, { issuer: did, signer }).then(jwt => - verifyJWT(jwt, { audience: did }) - .catch(error => - expect(error.message).toEqual( - 'JWT audience does not match your DID: aud: did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqY !== yours: did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX' - ) - ) - .then(p => expect(p).toBeFalsy()) - ) + it('rejects invalid audience', async () => { + const jwt = await createJWT({ aud }, { issuer: did, signer }) + expect(verifyJWT(jwt, { audience: did })) + .rejects + .toThrow(/JWT audience does not match your DID/) }) - it('rejects an invalid audience using callback_url where callback is wrong', () => { - return createJWT( + it('rejects an invalid audience using callback_url where callback is wrong', async () => { + const jwt = await createJWT( { aud: 'http://pututu.uport.me/unique' }, { issuer: did, signer } - ).then(jwt => - verifyJWT(jwt, { callbackUrl: 'http://pututu.uport.me/unique/1' }).catch( - error => - expect(error.message).toEqual( - 'JWT audience does not match the callback url: aud: http://pututu.uport.me/unique !== url: http://pututu.uport.me/unique/1' - ) - ) ) + expect(verifyJWT(jwt, { callbackUrl: 'http://pututu.uport.me/unique/1' })) + .rejects + .toThrow(/JWT audience does not match the callback url/) }) - it('rejects an invalid audience using callback_url where callback is missing', () => { - return createJWT( + it('rejects an invalid audience using callback_url where callback is missing', async () => { + const jwt = await createJWT( { aud: 'http://pututu.uport.me/unique' }, { issuer: did, signer } - ).then(jwt => - verifyJWT(jwt).catch(error => - expect(error.message).toEqual( - "JWT audience matching your callback url is required but one wasn't passed in" - ) - ) ) + expect(verifyJWT(jwt)) + .rejects + .toThrow('JWT audience matching your callback url is required but one wasn\'t passed in') }) - it('rejects invalid audience as no address is present', () => { - return createJWT({ aud }, { issuer: did, signer }).then(jwt => - verifyJWT(jwt) - .catch(error => - expect(error.message).toEqual( - 'JWT audience is required but your app address has not been configured' - ) - ) - .then(p => expect(p).toBeFalsy()) - ) + it('rejects invalid audience as no address is present', async () => { + const jwt = await createJWT({ aud }, { issuer: did, signer }) + expect(verifyJWT(jwt)) + .rejects + .toThrow('JWT audience is required but your app address has not been configured') }) }) @@ -545,9 +444,13 @@ describe('resolveAuthenticator()', () => { } describe('DID', () => { + afterEach(() => { + resolver.resolve = originalResolve + }) + describe('ES256K', () => { it('finds public key', async () => { - registerResolver((mnid, cb) => cb(null, singleKey)) + resolver.resolve = jest.fn().mockReturnValue(singleKey) const authenticators = await resolveAuthenticator(alg, did) return expect(authenticators).toEqual({ authenticators: [ecKey1], @@ -557,7 +460,7 @@ describe('resolveAuthenticator()', () => { }) it('filters out irrelevant public keys', async () => { - registerResolver((mnid, cb) => cb(null, multipleKeys)) + resolver.resolve = jest.fn().mockReturnValue(multipleKeys) const authenticators = await resolveAuthenticator(alg, did) return expect(authenticators).toEqual({ authenticators: [ecKey1, ecKey2, ecKey3], @@ -567,7 +470,7 @@ describe('resolveAuthenticator()', () => { }) it('only list authenticators able to authenticate a user', async () => { - registerResolver((mnid, cb) => cb(null, multipleKeys)) + resolver.resolve = jest.fn().mockReturnValue(multipleKeys) const authenticators = await resolveAuthenticator(alg, did, true) return expect(authenticators).toEqual({ authenticators: [ecKey1, ecKey2], @@ -577,7 +480,7 @@ describe('resolveAuthenticator()', () => { }) it('errors if no suitable public keys exist', async () => { - registerResolver((mnid, cb) => cb(null, unsupportedFormat)) + resolver.resolve = jest.fn().mockReturnValue(unsupportedFormat) return expect(resolveAuthenticator(alg, did)).rejects.toEqual( new Error( `DID document for ${did} does not have public keys for ${alg}` @@ -589,7 +492,7 @@ describe('resolveAuthenticator()', () => { describe('Ed25519', () => { const alg = 'Ed25519' it('filters out irrelevant public keys', async () => { - registerResolver((mnid, cb) => cb(null, multipleKeys)) + resolver.resolve = jest.fn().mockReturnValue(multipleKeys) const authenticators = await resolveAuthenticator(alg, did) return expect(authenticators).toEqual({ authenticators: [edKey, edKey2], @@ -599,7 +502,7 @@ describe('resolveAuthenticator()', () => { }) it('only list authenticators able to authenticate a user', async () => { - registerResolver((mnid, cb) => cb(null, multipleKeys)) + resolver.resolve = jest.fn().mockReturnValue(multipleKeys) const authenticators = await resolveAuthenticator(alg, did, true) return expect(authenticators).toEqual({ authenticators: [edKey], @@ -609,7 +512,7 @@ describe('resolveAuthenticator()', () => { }) it('errors if no suitable public keys exist', async () => { - registerResolver((mnid, cb) => cb(null, unsupportedFormat)) + resolver.resolve = jest.fn().mockReturnValue(unsupportedFormat) return expect(resolveAuthenticator(alg, did)).rejects.toEqual( new Error( `DID document for ${did} does not have public keys for ${alg}` @@ -619,7 +522,7 @@ describe('resolveAuthenticator()', () => { }) it('errors if no suitable public keys exist for authentication', async () => { - registerResolver((mnid, cb) => cb(null, singleKey)) + resolver.resolve = jest.fn().mockReturnValue(singleKey) return expect(resolveAuthenticator(alg, did, true)).rejects.toEqual( new Error( `DID document for ${did} does not have public keys suitable for authenticationg user` @@ -628,7 +531,7 @@ describe('resolveAuthenticator()', () => { }) it('errors if no public keys exist', async () => { - registerResolver((mnid, cb) => cb(null, noPublicKey)) + resolver.resolve = jest.fn().mockReturnValue(noPublicKey) return expect(resolveAuthenticator(alg, did)).rejects.toEqual( new Error( `DID document for ${did} does not have public keys for ${alg}` @@ -637,29 +540,31 @@ describe('resolveAuthenticator()', () => { }) it('errors if no DID document exists', async () => { - registerResolver((mnid, cb) => cb(null, null)) + resolver.resolve = jest.fn().mockReturnValue(null) return expect(resolveAuthenticator(alg, did)).rejects.toEqual( new Error(`Unable to resolve DID document for ${did}`) ) }) it('errors if no supported signature types exist', async () => { - registerResolver((mnid, cb) => cb(null, singleKey)) + resolver.resolve = jest.fn().mockReturnValue(singleKey) return expect(resolveAuthenticator('ESBAD', did)).rejects.toEqual( new Error(`No supported signature types for algorithm ESBAD`) ) }) }) - describe('MNID', () => { - it('converts MNID to DID and finds public key', async () => { - registerResolver((mnid, cb) => cb(null, singleKey)) - const authenticators = await resolveAuthenticator(alg, mnid) - return expect(authenticators).toEqual({ - authenticators: [ecKey1], - issuer: did, - doc: singleKey - }) + describe('normalizeDID', () => { + it('returns the value if it is already a did', () => { + expect(normalizeDID(did)).toEqual(did) + }) + it('converts an mnid into a did', () => { + expect(normalizeDID('2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX')).toEqual( + 'did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX' + ) + }) + it('throws if the value is neither a did nor an mnid', () => { + expect(() => normalizeDID('notadid!')).toThrow() }) }) }) diff --git a/src/__tests__/__snapshots__/JWT-test.ts.snap b/src/__tests__/__snapshots__/JWT-test.ts.snap index 50865fb0..93a1b955 100644 --- a/src/__tests__/__snapshots__/JWT-test.ts.snap +++ b/src/__tests__/__snapshots__/JWT-test.ts.snap @@ -2,39 +2,39 @@ exports[`createJWT() ES256K creates a JWT with correct format 1`] = ` Object { - "data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXSwiaXNzIjoiZGlkOnVwb3J0OjJuUXRpUUc2Q2dtMUdZVEJhYUtBZ3I3NnVZN2lTZXhVa3FYIn0", + "data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXSwiaXNzIjoiZGlkOmV0aHI6MHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0", "header": Object { "alg": "ES256K", "typ": "JWT", }, "payload": Object { "iat": 1485321133, - "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", + "iss": "did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74", "requested": Array [ "name", "phone", ], }, - "signature": "4zZKylwG3_1WPPd0HEpeOt1uUplyXhIGQ7G26wx0T1fnwovYcXQUA-FY3Cp13iA0XwMT8kIlxwsuMFCYMdw1sw", + "signature": "tU96omPNxCfQoEADOpLywXUDCMjKXOfTaG61EZwmfvHJrDFQhNbSDzCP2Pe7WdXySosTCuI1T-IQ6SddcWuj_A", } `; exports[`createJWT() ES256K creates a JWT with correct legacy format 1`] = ` Object { - "data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXSwiaXNzIjoiMm5RdGlRRzZDZ20xR1lUQmFhS0Fncjc2dVk3aVNleFVrcVgifQ", + "data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXSwiaXNzIjoiMHhmM2JlYWMzMGM0OThkOWUyNjg2NWYzNGZjYWE1N2RiYjkzNWIwZDc0In0", "header": Object { "alg": "ES256K", "typ": "JWT", }, "payload": Object { "iat": 1485321133, - "iss": "2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", + "iss": "0xf3beac30c498d9e26865f34fcaa57dbb935b0d74", "requested": Array [ "name", "phone", ], }, - "signature": "D1Tx-R9g-pyGAb4C486fx_1Scwf74JcoUC2xYRJ6cZFW9zwBs_NZEZg3bptrMfmNRao_2A2cbCncRKPpV7TycQ", + "signature": "Ma6o2byDEFmLxmqxJCOXgjAPZftSglFAzzKzv-3kBUkquDPWZyVYK0-vVhH-SbyasahIuHZCDGl4jQrW7hFsAw", } `; @@ -57,19 +57,11 @@ Object { } `; -exports[`verifyJWT() accepts a valid MNID audience 1`] = ` -Object { - "aud": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqY", - "iat": 1485321133, - "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", -} -`; - exports[`verifyJWT() accepts a valid audience 1`] = ` Object { - "aud": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqY", + "aud": "did:ethr:0x20c769ec9c0996ba7737a4826c2aaff00b1b2040", "iat": 1485321133, - "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", + "iss": "did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74", } `; @@ -77,7 +69,7 @@ exports[`verifyJWT() accepts a valid audience using callback_url 1`] = ` Object { "aud": "http://pututu.uport.me/unique", "iat": 1485321133, - "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", + "iss": "did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74", } `; @@ -85,7 +77,7 @@ exports[`verifyJWT() handles ES256K-R algorithm 1`] = ` Object { "hello": "world", "iat": 1485321133, - "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", + "iss": "did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74", } `; @@ -93,41 +85,14 @@ exports[`verifyJWT() handles ES256K-R algorithm with ethereum address 1`] = ` Object { "hello": "world", "iat": 1485321133, - "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqY", -} -`; - -exports[`verifyJWT() nacl-did jwt verifies the JWT and return correct profile 1`] = ` -Object { - "@context": "https://w3id.org/did/v1", - "authentication": Array [ - Object { - "publicKey": "did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU#key1", - "type": "ED25519SigningAuthentication", - }, - ], - "id": "did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU", - "publicKey": Array [ - Object { - "id": "did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU#key1", - "owner": "did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU", - "publicKeyBase64": "BvrB8iJAz/1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU=", - "type": "ED25519SignatureVerification", - }, - Object { - "id": "did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU#key2", - "owner": "did:nacl:BvrB8iJAz_1jfq1mRxiEKfr9qcnLfq5DOGrBf2ERUHU", - "publicKeyBase64": "U45OP5q0lvh+WRILEatlJgmgcy0Jetxb2uYdokK5H3k=", - "type": "Curve25519EncryptionPublicKey", - }, - ], + "iss": "did:ethr:0x20c769ec9c0996ba7737a4826c2aaff00b1b2040", } `; exports[`verifyJWT() pregenerated JWT verifies the JWT and return correct payload 1`] = ` Object { "iat": 1485321133, - "iss": "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", + "iss": "did:ethr:0x90e45d75bd1246e0924872018647dba996a8e7b9", "requested": Array [ "name", "phone",