diff --git a/README.md b/README.md index 979ed506..426646f5 100644 --- a/README.md +++ b/README.md @@ -86,14 +86,18 @@ console.log(isValid(fragments)); // display true "type": "ISSUER_IDENTITY" } ] - ``` ## Advanced usage ### Environment Variables -- `INFURA_API_KEY`: let you provide your own `INFURA` API key. +- `PROVIDER_API_KEY`: let you provide your own PROVIDER API key. +- `PROVIDER_ENDPOINT_URL`: let you provide your preferred JSON-RPC HTTP API URL. +- `PROVIDER_NETWORK`: let you specify the network to use, i.e. "homestead", "mainnet", "ropsten", "rinkeby". +- `PROVIDER_ENDPOINT_TYPE`: let you specify the provider to use, i.e. "infura", "alchemy", "jsonrpc". + +_Provider that is supported: Infura, EtherScan, Alchemy, JSON-RPC_ ### Switching network @@ -119,20 +123,17 @@ const verify = verificationBuilder(openAttestationVerifiers, { network: "ropsten `oa-verify` exposes a method, called `createResolver` that allows you to easily create custom resolvers, to resolve DIDs: ```ts -import { - createResolver, - verificationBuilder, - openAttestationVerifiers -} from '@govtechsg/oa-verify'; +import { createResolver, verificationBuilder, openAttestationVerifiers } from "@govtechsg/oa-verify"; const resolver = createResolver({ - networks: [{ name: 'my-network', rpcUrl: 'https://my-private-chain/besu', registry: '0xaE5a9b9...' }], + networks: [{ name: "my-network", rpcUrl: "https://my-private-chain/besu", registry: "0xaE5a9b9..." }], }); const verify = verificationBuilder(openAttestationVerifiers, { resolver }); ``` At the moment, oa-verify supports two did resolvers: + - [web-did-resolver](https://github.com/decentralized-identity/web-did-resolver#readme) - [ethd-did-resolver](https://github.com/decentralized-identity/ethr-did-resolver) @@ -193,6 +194,56 @@ isValid(fragments); // display false because ISSUER_IDENTITY is INVALID isValid(fragments, ["DOCUMENT_INTEGRITY", "DOCUMENT_STATUS"]); // display true because those types are VALID ``` +## Provider + +You may generate a provider using the provider generator, it supports `INFURA`, `ALCHEMY`, `ETHERSCAN` and `JsonRPC` provider. + +It requires a set of options: + +- `network`: The _network_ may be specified as a **string** for a common network name, i.e. "homestead", "mainnet", "ropsten", "rinkeby". +- `provider`: The _provider_ may be specified as a **string**, i.e. "infura", "alchemy" or "jsonrpc". +- `url`: The _url_ may be specified as a **string** in which is being used to connect to a JSON-RPC HTTP API +- `apiKey`: The _apiKey_ may be specified as a **string** for use together with the provider. If no apiKey is provided, a default shared API key will be used, which may result in reduced performance and throttled requests. + +### Example + +The most basic way to use: + +``` +import { utils } from "@govtechsg/oa-verify"; +const provider = utils.generateProvider(); +// This will generate an infura provider using the default values. +``` + +Alternate way 1 (with environment variables): + +``` +// environment file +PROVIDER_NETWORK="ropsten" +PROVIDER_ENDPOINT_TYPE="infura" +PROVIDER_ENDPOINT_URL="http://jsonrpc.com" +PROVIDER_API_KEY="ajdh1j23" + +// provider file +import { utils } from "@govtechsg/oa-verify"; +const provider = utils.generateProvider(); +// This will use the environment variables declared in the files automatically. +``` + +Alternate way 2 (passing values in as parameters): + +``` +import { utils } from "@govtechsg/oa-verify"; +const providerOptions = { + network: "ropsten", + providerType: "infura", + apiKey: "abdfddsfe23232" +}; +const provider = utils.generateProvider(providerOptions); +// This will generate a provider based on the options provided. +// NOTE: by using this way, it will override all environment variables and default values. +``` + ## Utils and types ### Overview @@ -230,6 +281,7 @@ if(utils.isValidFragment(fragment)) { Note that in the example above, using `utils.isValidFragment` might be unnecessary. It's possible to use directly `ValidTokenRegistryDataV2.guard` over the data. ### List of utilities + - `getOpenAttestationHashFragment` - `getOpenAttestationDidSignedDocumentStatusFragment` - `getOpenAttestationEthereumDocumentStoreStatusFragment` @@ -245,7 +297,6 @@ Note that in the example above, using `utils.isValidFragment` might be unnecessa - `isErrorFragment`: type guard to filter only `ERROR` fragment type - `isSkippedFragment`: type guard to filter only `SKIPPED` fragment type - ## Development For generating of test documents (for v3) you may use the script at `scripts/generate.v3.ts` by running `npm run generate:v3`. diff --git a/src/common/utils.test.ts b/src/common/utils.test.ts index 210a5ae9..8afa507a 100644 --- a/src/common/utils.test.ts +++ b/src/common/utils.test.ts @@ -10,8 +10,10 @@ import { getOpenAttestationEthereumDocumentStoreStatusFragment, getOpenAttestationEthereumTokenRegistryStatusFragment, getOpenAttestationHashFragment, + generateProvider, } from "./utils"; import { AllVerificationFragment } from ".."; +import { ProviderDetails } from "../types/core"; const fragments: AllVerificationFragment[] = [ { @@ -250,3 +252,148 @@ describe("getFragmentsByType", () => { `); }); }); + +describe("generateProvider", () => { + beforeEach(() => { + jest.resetModules(); + process.env = { + PROVIDER_NETWORK: "", + PROVIDER_API_KEY: "", + PROVIDER_ENDPOINT_TYPE: "", + PROVIDER_ENDPOINT_URL: "", + }; + // eslint-disable-next-line @typescript-eslint/no-empty-function + jest.spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + jest.spyOn(console, "warn").mockRestore(); + }); + + it("should use the details provided as top priority", () => { + const options = { + network: "ropsten", + providerType: "infura", + apiKey: "bb46da3f80e040e8ab73c0a9ff365d18", + } as ProviderDetails; + const provider = generateProvider(options) as any; + + expect(provider?._network?.name).toEqual("ropsten"); + expect(provider?.apiKey).toEqual("bb46da3f80e040e8ab73c0a9ff365d18"); + expect(provider?.connection?.url).toMatch(/(infura)/i); + }); + + it("should use the default values to generate the provider if user did not specify any provider details", () => { + const provider = generateProvider() as any; + expect(provider?._network?.name).toEqual("homestead"); + expect(provider?.apiKey).toEqual("84842078b09946638c03157f83405213"); + expect(provider?.connection?.url).toMatch(/(infura)/i); + }); + + it("should use the default alchemy apiKey if no apiKey specified", () => { + const options = { + network: "ropsten", + providerType: "alchemy", + } as ProviderDetails; + const provider = generateProvider(options) as any; + expect(provider?.connection?.url).toMatch(/(alchemy)/i); + expect(provider?.apiKey).toEqual("_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC"); + }); + + it("should use the default jsonrpc url which is localhost:8545", () => { + const options = { + network: "ropsten", + providerType: "jsonrpc", + } as ProviderDetails; + const provider = generateProvider(options) as any; + expect(provider?.connection?.url).toMatch(/(localhost:8545)/i); + }); + + it("should still generate a provider even if only one option (network) is provided", () => { + const options = { network: "ropsten" } as ProviderDetails; + const provider = generateProvider(options) as any; + expect(provider?._network?.name).toEqual("ropsten"); + expect(provider?.apiKey).toEqual("84842078b09946638c03157f83405213"); + expect(provider?.connection?.url).toMatch(/(infura)/i); + }); + + it("should still generate a provider even if only one option (provider) is provided", () => { + const options = { providerType: "infura" } as ProviderDetails; + const provider = generateProvider(options) as any; + expect(provider?._network?.name).toEqual("homestead"); + expect(provider?.apiKey).toEqual("84842078b09946638c03157f83405213"); + expect(provider?.connection?.url).toMatch(/(infura)/i); + }); + + it("should still generate a provider even if only one option (url) is provided", () => { + const options = { url: "www.123.com" } as ProviderDetails; + const provider = generateProvider(options) as any; + expect(provider?.connection?.url).toMatch(/(www.123.com)/i); + }); + + it("should throw an error and not generate a provider when only one option (apikey) is provided", () => { + const options = { apiKey: "abc123" } as ProviderDetails; + expect(() => { + generateProvider(options); + }).toThrowError( + "We could not link the apiKey provided to a provider, please state the provider to use in the parameter." + ); + }); + + it("should throw an error when if process.env is using the wrong value for PROVIDER", () => { + process.env.PROVIDER_ENDPOINT_TYPE = "ABC"; + expect(() => generateProvider()).toThrowError( + "The provider provided is not on the list of providers. Please use one of the following: infura, alchemy or jsonrpc." + ); + }); + + it("should use the process.env values if there is one, should not use the default values, for infura test case", () => { + process.env.PROVIDER_NETWORK = "rinkeby"; + process.env.PROVIDER_API_KEY = "env123123"; + + const provider = generateProvider() as any; + expect(provider?._network?.name).toEqual("rinkeby"); + expect(provider?._network?.name).not.toEqual("mainnet"); + expect(provider?.apiKey).toEqual("env123123"); + expect(provider?.apiKey).not.toEqual("bb46da3f80e040e8ab73c0a9ff365d18"); + expect(provider?.connection?.url).toMatch(/(infura)/i); + }); + + it("should use the process.env values if there is one, should not use the default values, for alchemy test case", () => { + process.env.PROVIDER_NETWORK = "rinkeby"; + process.env.PROVIDER_API_KEY = "env789789"; + + const options = { + providerType: "alchemy", + } as ProviderDetails; + const provider = generateProvider(options) as any; + expect(provider?._network?.name).toEqual("rinkeby"); + expect(provider?._network?.name).not.toEqual("mainnet"); + expect(provider?.apiKey).toEqual("env789789"); + expect(provider?.apiKey).not.toEqual("OlOgD-8qs5l3pQm-B_fcrMAmHTmAwkGj"); + expect(provider?.connection?.url).toMatch(/(alchemy)/i); + }); + + it("should use the process.env values if there is one, should not use the default values, for Json RPC test case", () => { + process.env.PROVIDER_ENDPOINT_URL = "www.1234.com"; + + const options = { + providerType: "jsonrpc", + } as ProviderDetails; + const provider = generateProvider(options) as any; + expect(provider?.connection?.url).toMatch(/(www.1234.com)/i); + }); + + it("should override the process.env value with the function parameter value", () => { + process.env.PROVIDER_NETWORK = "rinkeby"; + process.env.PROVIDER_API_KEY = "env789789"; + + const options = { network: "ropsten", providerType: "alchemy", apiKey: "abc123" } as ProviderDetails; + const provider = generateProvider(options) as any; + expect(provider?._network?.name).toEqual("ropsten"); + expect(provider?._network?.name).not.toEqual("rinkeby"); + expect(provider?.apiKey).toEqual("abc123"); + expect(provider?.apiKey).not.toEqual("env789789"); + expect(provider?.connection?.url).toMatch(/(alchemy)/i); + }); +}); diff --git a/src/common/utils.ts b/src/common/utils.ts index f08a0256..de3d5c6f 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,11 +1,13 @@ import { providers } from "ethers"; +import { INFURA_API_KEY } from "../config"; import { VerificationBuilderOptions, VerificationBuilderOptionsWithNetwork, VerificationFragment, VerificationFragmentType, + ProviderDetails, + providerType, } from "../types/core"; -import { INFURA_API_KEY } from "../config"; import { OpenAttestationHashVerificationFragment } from "../verifiers/documentIntegrity/hash/openAttestationHash.type"; import { OpenAttestationDidSignedDocumentStatusVerificationFragment } from "../verifiers/documentStatus/didSigned/didSignedDocumentStatus.type"; import { OpenAttestationEthereumDocumentStoreStatusFragment } from "../verifiers/documentStatus/documentStore/ethereumDocumentStoreStatus.type"; @@ -15,20 +17,74 @@ import { OpenAttestationDnsDidIdentityProofVerificationFragment } from "../verif import { OpenAttestationDnsTxtIdentityProofVerificationFragment } from "../verifiers/issuerIdentity/dnsTxt/openAttestationDnsTxt.type"; export const getDefaultProvider = (options: VerificationBuilderOptionsWithNetwork): providers.Provider => { + const network = options.network || process.env.PROVIDER_NETWORK || "homestead"; + const providerType = (process.env.PROVIDER_ENDPOINT_TYPE as providerType) || "infura"; + const apiKey = process.env.PROVIDER_API_KEY || (providerType === "infura" && INFURA_API_KEY) || ""; // create infura provider to get connection information // we then use StaticJsonRpcProvider so that we can set our own custom limit - const uselessProvider = new providers.InfuraProvider(options.network, INFURA_API_KEY); + const uselessProvider = generateProvider({ + providerType, + network, + apiKey, + }) as providers.UrlJsonRpcProvider; const connection = { ...uselessProvider.connection, throttleLimit: 3, // default is 12 which may retry 12 times for 2 minutes on 429 failures }; - return new providers.StaticJsonRpcProvider(connection, options.network); + return new providers.StaticJsonRpcProvider(connection, network); }; +// getProvider is a function to get an existing provider or to get a Default provider, when given the options export const getProvider = (options: VerificationBuilderOptions): providers.Provider => { return options.provider ?? getDefaultProvider(options); }; +/** + * Generate Provider generates a provider based on the defined options or your env var, if no options or env var was detected, it will generate a provider based on the default values. + * Generate Provider using the following options: (if no option is specified it will use the default values) + * @param {Object} ProviderDetails - Details to use for the function to successfully generate a provider. + * @param {string} ProviderDetails.network - The network in which the provider is connected to, i.e. "homestead", "mainnet", "ropsten", "rinkeby" + * @param {string} ProviderDetails.providerType - Specify which provider to use: "infura", "alchemy" or "jsonrpc" + * @param {string} ProviderDetails.url - Specify which url for JsonRPC to connect to, if not specified will connect to localhost:8545 + * @param {string} ProviderDetails.apiKey - If no apiKey is provided, a default shared API key will be used, which may result in reduced performance and throttled requests. + */ +export const generateProvider = (options?: ProviderDetails): providers.Provider => { + if (!!options && Object.keys(options).length === 1 && options.apiKey) { + throw new Error( + "We could not link the apiKey provided to a provider, please state the provider to use in the parameter." + ); + } + + const network = options?.network || process.env.PROVIDER_NETWORK || "homestead"; + const provider = options?.providerType || process.env.PROVIDER_ENDPOINT_TYPE || "infura"; + const url = options?.url || process.env.PROVIDER_ENDPOINT_URL || ""; + const apiKey = + options?.apiKey || (provider === "infura" && process.env.INFURA_API_KEY) || process.env.PROVIDER_API_KEY || ""; + !apiKey && + console.warn( + "You are using oa-verify default configuration for provider, which is not suitable for production environment. Please make sure that you configured the library correctly." + ); + + if (!!options && Object.keys(options).length === 1 && url) { + return new providers.JsonRpcProvider(url); + } + switch (provider) { + case "infura": + return apiKey ? new providers.InfuraProvider(network, apiKey) : new providers.InfuraProvider(network); + + case "alchemy": + return apiKey ? new providers.AlchemyProvider(network, apiKey) : new providers.AlchemyProvider(network); + + case "jsonrpc": + return new providers.JsonRpcProvider(url); + + default: + throw new Error( + "The provider provided is not on the list of providers. Please use one of the following: infura, alchemy or jsonrpc." + ); + } +}; + /** * Simple typed utility to return a fragment depending on the name * @param name diff --git a/src/did/resolver.test.ts b/src/did/resolver.test.ts index 895aa604..94b323f3 100644 --- a/src/did/resolver.test.ts +++ b/src/did/resolver.test.ts @@ -3,7 +3,7 @@ import { v3 } from "@govtechsg/open-attestation"; import { openAttestationDidIdentityProof } from "../verifiers/issuerIdentity/did/didIdentityProof"; import { verificationBuilder } from "../verifiers/verificationBuilder"; import { INFURA_API_KEY } from "../config"; -import { createResolver, EthrResolverConfig, resolve } from "./resolver"; +import { createResolver, EthrResolverConfig, resolve, getProviderConfig } from "./resolver"; const didDoc = { "@context": [ @@ -108,3 +108,45 @@ describe("custom resolver", () => { `); }, 10000); }); + +describe("getProviderConfig", () => { + beforeEach(() => { + jest.resetModules(); + process.env = { + PROVIDER_NETWORK: "", + PROVIDER_API_KEY: "", + PROVIDER_ENDPOINT_TYPE: "", + PROVIDER_ENDPOINT_URL: "", + }; + // eslint-disable-next-line @typescript-eslint/no-empty-function + jest.spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + jest.spyOn(console, "warn").mockRestore(); + }); + + it("should use env variables for provider config properties for didResolver", () => { + process.env.PROVIDER_ENDPOINT_TYPE = "alchemy"; + process.env.PROVIDER_NETWORK = "ropsten"; + + expect(getProviderConfig()).toEqual({ + networks: [{ name: "ropsten", rpcUrl: "https://eth-ropsten.alchemyapi.io/v2/_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC" }], + }); + }); + + it("should set default when provider type is jsonrpc", () => { + process.env.PROVIDER_ENDPOINT_TYPE = "jsonrpc"; + process.env.PROVIDER_NETWORK = "ropsten"; + + expect(getProviderConfig()).toEqual({ + networks: [{ name: "mainnet", rpcUrl: "https://mainnet.infura.io/v3/bb46da3f80e040e8ab73c0a9ff365d18" }], + }); + }); + + it("should set defaults when no connection url and network is found in provider parameter object", () => { + expect(getProviderConfig()).toEqual({ + networks: [{ name: "mainnet", rpcUrl: "https://mainnet.infura.io/v3/84842078b09946638c03157f83405213" }], + }); + }); +}); diff --git a/src/did/resolver.ts b/src/did/resolver.ts index d15a48aa..e97d9979 100644 --- a/src/did/resolver.ts +++ b/src/did/resolver.ts @@ -3,6 +3,7 @@ import { getResolver as ethrGetResolver } from "ethr-did-resolver"; import { getResolver as webGetResolver } from "web-did-resolver"; import NodeCache from "node-cache"; import { INFURA_API_KEY } from "../config"; +import { generateProvider } from "../common/utils"; export interface EthrResolverConfig { networks: Array<{ @@ -12,13 +13,23 @@ export interface EthrResolverConfig { }>; } -const providerConfig = { - networks: [{ name: "mainnet", rpcUrl: `https://mainnet.infura.io/v3/${INFURA_API_KEY}` }], +export const getProviderConfig = () => { + const provider = generateProvider() as any; + const rpcUrl = provider?.connection?.url || ""; + const networkName = provider?._network?.name === "homestead" ? "mainnet" : provider?._network?.name || ""; + + if (!rpcUrl || !networkName) { + return { networks: [{ name: "mainnet", rpcUrl: `https://mainnet.infura.io/v3/${INFURA_API_KEY}` }] }; + } + + return { + networks: [{ name: networkName, rpcUrl: rpcUrl }], + }; }; const didResolutionCache = new NodeCache({ stdTTL: 5 * 60 }); // 5 min -const defaultResolver = new Resolver({ ...ethrGetResolver(providerConfig), ...webGetResolver() }); +const defaultResolver = new Resolver({ ...ethrGetResolver(getProviderConfig()), ...webGetResolver() }); export const createResolver = ({ ethrResolverConfig }: { ethrResolverConfig?: EthrResolverConfig }): Resolver => { return ethrResolverConfig diff --git a/src/index.ts b/src/index.ts index 67a879e7..35b3dbb8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,10 @@ import { openAttestationDnsDidIdentityProof } from "./verifiers/issuerIdentity/d import { createResolver } from "./did/resolver"; import { getIdentifier } from "./getIdentifier"; import * as utils from "./common/utils"; +import util from "util"; + +// eslint-disable-next-line @typescript-eslint/no-empty-function +util.deprecate(function infuraApiKey() {}, "'INFURA_API_KEY' has been deprecated, please use 'PROVIDER_API_KEY'."); const openAttestationVerifiers = [ openAttestationHash, @@ -22,7 +26,7 @@ const openAttestationVerifiers = [ ]; const defaultBuilderOption = { - network: "homestead", + network: process.env.PROVIDER_NETWORK || "homestead", }; const verify = verificationBuilder(openAttestationVerifiers, defaultBuilderOption); diff --git a/src/types/core.ts b/src/types/core.ts index d0770ce7..a668aa7b 100644 --- a/src/types/core.ts +++ b/src/types/core.ts @@ -107,3 +107,12 @@ export type DocumentsToVerify = | WrappedDocument | WrappedDocument | SignedWrappedDocument; + +export type providerType = "alchemy" | "infura" | "jsonrpc"; + +export interface ProviderDetails { + network?: string; + providerType?: providerType; + url?: string; + apiKey?: string; +} diff --git a/src/verifiers/verificationBuilder.ts b/src/verifiers/verificationBuilder.ts index f0e98923..4b07c475 100644 --- a/src/verifiers/verificationBuilder.ts +++ b/src/verifiers/verificationBuilder.ts @@ -22,7 +22,12 @@ export const verificationBuilder = >( builderOptions: VerificationBuilderOptions ) => (document: DocumentsToVerify, promisesCallback?: PromiseCallback): Promise => { // if the user didn't configure an API key and didn't configure a provider or a resolver, then he will likely use a development key. We then warn him once, that he may need to configure things properly, especially for production - if (displayWarning && (!builderOptions.resolver || !builderOptions.provider) && !process.env.INFURA_API_KEY) { + if ( + displayWarning && + (!builderOptions.resolver || !builderOptions.provider) && + !process.env.INFURA_API_KEY && + !process.env.PROVIDER_API_KEY + ) { displayWarning = false; console.warn( "You are using oa-verify default configuration, which is not suitable for production environment. Please make sure that you configured the library correctly." diff --git a/src/verify.v2.integration.errors.test.ts b/src/verify.v2.integration.errors.test.ts index 09980553..aa608dea 100644 --- a/src/verify.v2.integration.errors.test.ts +++ b/src/verify.v2.integration.errors.test.ts @@ -14,10 +14,20 @@ const verifyRopsten = verificationBuilder(openAttestationVerifiers, { network: " describe("Handling HTTP response errors", () => { const server = setupServer(); // Placing the following tests in a separate block due to how msw intercepts ALL connections beforeAll(() => server.listen()); // Enable API mocking before tests + beforeEach(() => { + jest.resetModules(); + process.env = { + PROVIDER_NETWORK: "", + PROVIDER_API_KEY: "", + PROVIDER_ENDPOINT_TYPE: "", + PROVIDER_ENDPOINT_URL: "", + }; + }); afterEach(() => server.resetHandlers()); // Reset any runtime request handlers we may add during the tests afterAll(() => server.close()); // Disable API mocking after the tests are done it("should return SERVER_ERROR when Ethers cannot connect to Infura with a valid certificate (HTTP 429)", async () => { + process.env.PROVIDER_API_KEY = INFURA_API_KEY; server.use( rest.post(`https://mainnet.infura.io/v3/${INFURA_API_KEY}`, (req, res, ctx) => { return res( @@ -94,6 +104,7 @@ describe("Handling HTTP response errors", () => { expect(isValid(results, ["DOCUMENT_STATUS"])).toStrictEqual(false); // Because of SERVER_ERROR }, 60000); it("should return SERVER_ERROR when Ethers cannot connect to Infura with a valid certificate (HTTP 502)", async () => { + process.env.PROVIDER_API_KEY = INFURA_API_KEY; server.use( rest.post(`https://mainnet.infura.io/v3/${INFURA_API_KEY}`, (req, res, ctx) => { return res( @@ -170,6 +181,7 @@ describe("Handling HTTP response errors", () => { expect(isValid(results, ["DOCUMENT_STATUS"])).toStrictEqual(false); // Because of SERVER_ERROR }); it("should return SERVER_ERROR when Ethers cannot connect to Infura with an invalid certificate (HTTP 429)", async () => { + process.env.PROVIDER_API_KEY = INFURA_API_KEY; // NOTE: Purpose of this test is to use a mainnet cert on ropsten. The mainnet cert store is perfectly valid, but does not exist on ropsten. server.use( rest.post(`https://ropsten.infura.io/v3/${INFURA_API_KEY}`, (req, res, ctx) => { @@ -247,6 +259,7 @@ describe("Handling HTTP response errors", () => { expect(isValid(results, ["DOCUMENT_STATUS"])).toStrictEqual(false); // Because of SERVER_ERROR }); it("should return SERVER_ERROR when Ethers cannot connect to Infura with an invalid certificate (HTTP 502)", async () => { + process.env.PROVIDER_API_KEY = INFURA_API_KEY; // NOTE: Purpose of this test is to use a mainnet cert on ropsten. The mainnet cert store is perfectly valid, but does not exist on ropsten. server.use( rest.post(`https://ropsten.infura.io/v3/${INFURA_API_KEY}`, (req, res, ctx) => { diff --git a/src/verify.v2.integration.test.ts b/src/verify.v2.integration.test.ts index 0e2c3a0d..739548c9 100644 --- a/src/verify.v2.integration.test.ts +++ b/src/verify.v2.integration.test.ts @@ -16,6 +16,7 @@ import { tamperedDocumentWithInvalidCertificateStore, } from "../test/fixtures/v2/tamperedDocument"; import { documentRopstenValidWithCertificateStore } from "../test/fixtures/v2/documentRopstenValidWithCertificateStore"; +import { documentRopstenValidWithDocumentStore } from "../test/fixtures/v2/documentRopstenValidWithDocumentStore"; import { documentRopstenValidWithToken } from "../test/fixtures/v2/documentRopstenValidWithToken"; import { documentRopstenRevokedWithToken } from "../test/fixtures/v2/documentRopstenRevokedWithToken"; import { documentRopstenRevokedWithDocumentStore } from "../test/fixtures/v2/documentRopstenRevokedWithDocumentStore"; @@ -47,8 +48,23 @@ const verifyRopsten = verificationBuilder(openAttestationVerifiers, { network: " const verifyRinkeby = verificationBuilder(openAttestationVerifiers, { network: "rinkeby" }); describe("verify(integration)", () => { + let defaultEnvironment: NodeJS.ProcessEnv; + beforeEach(() => { + jest.resetModules(); + defaultEnvironment = process.env; + process.env = Object.assign(process.env, { + PROVIDER_NETWORK: "", + PROVIDER_API_KEY: "", + PROVIDER_ENDPOINT_TYPE: "", + PROVIDER_ENDPOINT_URL: "", + }); + // eslint-disable-next-line @typescript-eslint/no-empty-function + jest.spyOn(console, "warn").mockImplementation(() => {}); + }); + afterEach(() => { - delete process.env.ETHEREUM_PROVIDER; + process.env = defaultEnvironment; + jest.spyOn(console, "warn").mockRestore(); }); it("should skip all verifiers when the document is an empty object", async () => { const fragments = await verifyRopsten({} as any); @@ -121,6 +137,7 @@ describe("verify(integration)", () => { expect(isValid(fragments, ["DOCUMENT_STATUS"])).toStrictEqual(false); expect(isValid(fragments, ["ISSUER_IDENTITY"])).toStrictEqual(false); }); + it("should fail for everything when document's hash is invalid and certificate store is invalid", async () => { const results = await verifyHomestead(tamperedDocumentWithCertificateStore); expect(results).toMatchInlineSnapshot(` @@ -608,6 +625,7 @@ describe("verify(integration)", () => { `); expect(isValid(results)).toStrictEqual(true); }); + it("should be invalid with a merkle root that is odd-length", async () => { const results = await verifyHomestead(documentMainnetInvalidWithOddLengthMerkleRoot); expect(results).toMatchInlineSnapshot(` @@ -1227,7 +1245,7 @@ describe("verify(integration)", () => { expect(isValid(results, ["DOCUMENT_INTEGRITY"])).toStrictEqual(true); expect(isValid(results, ["ISSUER_IDENTITY"])).toStrictEqual(false); expect(isValid(results)).toStrictEqual(false); - }); + }, 10000); it("should pass with document signed directly with DID with custom verifier", async () => { const customVerify = verificationBuilder([...openAttestationVerifiers, openAttestationDidIdentityProof], { @@ -1562,6 +1580,7 @@ describe("verify(integration)", () => { expect(isValid(results, ["ISSUER_IDENTITY"])).toStrictEqual(false); expect(isValid(results)).toStrictEqual(false); }); + it("should return valid fragments for document issued correctly with DID & using DID identity proof, but not revoked on a document store", async () => { const customVerify = verificationBuilder([...openAttestationVerifiers, openAttestationDidIdentityProof], { network: "ropsten", @@ -1656,6 +1675,7 @@ describe("verify(integration)", () => { expect(isValid(fragments, ["ISSUER_IDENTITY"])).toStrictEqual(true); expect(isValid(fragments)).toStrictEqual(true); }); + it("should return valid fragments for document issued correctly with DID & using DNS-DID identity proof, but not revoked on a document store", async () => { const fragments = await verifyRopsten(v2DnsDidSignedRevocationStoreNotRevoked); expect(fragments).toMatchInlineSnapshot(` @@ -1738,6 +1758,7 @@ describe("verify(integration)", () => { expect(isValid(fragments, ["ISSUER_IDENTITY"])).toStrictEqual(true); expect(isValid(fragments)).toStrictEqual(true); }); + it("should return invalid fragments for DID documents that have been revoked", async () => { const customVerify = verificationBuilder([...openAttestationVerifiers, openAttestationDidIdentityProof], { network: "ropsten", @@ -1784,6 +1805,7 @@ describe("verify(integration)", () => { ] `); }); + it("should return invalid fragments for DID documents, using DNS-DID identity proof that have been revoked", async () => { const fragments = await verifyRopsten(v2DnsDidSignedRevocationStoreButRevoked); expect(isValid(fragments, ["DOCUMENT_STATUS"])).toStrictEqual(false); @@ -1827,4 +1849,339 @@ describe("verify(integration)", () => { ] `); }); + + it("should return the correct fragments even when process.env is used for out of the box verify for document with document store", async () => { + // simulate loading process.env from .env file + process.env.PROVIDER_NETWORK = "ropsten"; + process.env.PROVIDER_ENDPOINT_TYPE = "alchemy"; + const defaultBuilderOption = { + network: process.env.PROVIDER_NETWORK || "homestead", + }; + const verification = verificationBuilder(openAttestationVerifiers, defaultBuilderOption); + const fragments = await verification(documentRopstenValidWithDocumentStore); + expect(fragments).toMatchInlineSnapshot(` + Array [ + Object { + "data": true, + "name": "OpenAttestationHash", + "status": "VALID", + "type": "DOCUMENT_INTEGRITY", + }, + Object { + "name": "OpenAttestationEthereumTokenRegistryStatus", + "reason": Object { + "code": 4, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"tokenRegistry\\" property or TOKEN_REGISTRY method", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "data": Object { + "details": Object { + "issuance": Array [ + Object { + "address": "0x8Fc57204c35fb9317D91285eF52D6b892EC08cD3", + "issued": true, + }, + ], + "revocation": Array [ + Object { + "address": "0x8Fc57204c35fb9317D91285eF52D6b892EC08cD3", + "revoked": false, + }, + ], + }, + "issuedOnAll": true, + "revokedOnAny": false, + }, + "name": "OpenAttestationEthereumDocumentStoreStatus", + "status": "VALID", + "type": "DOCUMENT_STATUS", + }, + Object { + "name": "OpenAttestationDidSignedDocumentStatus", + "reason": Object { + "code": 0, + "codeString": "SKIPPED", + "message": "Document was not signed by DID directly", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "data": Array [ + Object { + "location": "example.tradetrust.io", + "reason": Object { + "code": 4, + "codeString": "MATCHING_RECORD_NOT_FOUND", + "message": "Matching DNS record not found for 0x8Fc57204c35fb9317D91285eF52D6b892EC08cD3", + }, + "status": "INVALID", + "value": "0x8Fc57204c35fb9317D91285eF52D6b892EC08cD3", + }, + ], + "name": "OpenAttestationDnsTxtIdentityProof", + "reason": Object { + "code": 4, + "codeString": "MATCHING_RECORD_NOT_FOUND", + "message": "Matching DNS record not found for 0x8Fc57204c35fb9317D91285eF52D6b892EC08cD3", + }, + "status": "INVALID", + "type": "ISSUER_IDENTITY", + }, + Object { + "name": "OpenAttestationDnsDidIdentityProof", + "reason": Object { + "code": 0, + "codeString": "SKIPPED", + "message": "Document was not issued using DNS-DID", + }, + "status": "SKIPPED", + "type": "ISSUER_IDENTITY", + }, + ] + `); + }); + + it("should use the defaults to connect to provider even when process.env is not there for document with document store", async () => { + const defaultBuilderOption = { + network: process.env.PROVIDER_NETWORK || "homestead", + }; + const verification = verificationBuilder(openAttestationVerifiers, defaultBuilderOption); + const fragments = await verification(documentMainnetValidWithCertificateStore); + expect(fragments).toMatchInlineSnapshot(` + Array [ + Object { + "data": true, + "name": "OpenAttestationHash", + "status": "VALID", + "type": "DOCUMENT_INTEGRITY", + }, + Object { + "name": "OpenAttestationEthereumTokenRegistryStatus", + "reason": Object { + "code": 4, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"tokenRegistry\\" property or TOKEN_REGISTRY method", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "data": Object { + "details": Object { + "issuance": Array [ + Object { + "address": "0x007d40224f6562461633ccfbaffd359ebb2fc9ba", + "issued": true, + }, + ], + "revocation": Array [ + Object { + "address": "0x007d40224f6562461633ccfbaffd359ebb2fc9ba", + "revoked": false, + }, + ], + }, + "issuedOnAll": true, + "revokedOnAny": false, + }, + "name": "OpenAttestationEthereumDocumentStoreStatus", + "status": "VALID", + "type": "DOCUMENT_STATUS", + }, + Object { + "name": "OpenAttestationDidSignedDocumentStatus", + "reason": Object { + "code": 0, + "codeString": "SKIPPED", + "message": "Document was not signed by DID directly", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "name": "OpenAttestationDnsTxtIdentityProof", + "reason": Object { + "code": 2, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"documentStore\\" / \\"tokenRegistry\\" property or doesn't use DNS-TXT type", + }, + "status": "SKIPPED", + "type": "ISSUER_IDENTITY", + }, + Object { + "name": "OpenAttestationDnsDidIdentityProof", + "reason": Object { + "code": 0, + "codeString": "SKIPPED", + "message": "Document was not issued using DNS-DID", + }, + "status": "SKIPPED", + "type": "ISSUER_IDENTITY", + }, + ] + `); + }); + + it("should return the correct fragments when using process.env variables for did resolver", async () => { + // simulate loading process.env from .env file + process.env.PROVIDER_NETWORK = "ropsten"; + process.env.PROVIDER_ENDPOINT_TYPE = "alchemy"; + const defaultBuilderOption = { + network: process.env.PROVIDER_NETWORK || "homestead", + }; + const verification = verificationBuilder(openAttestationVerifiers, defaultBuilderOption); + const didFragments = await verification(documentDidSigned); + const dnsDidFragments = await verification(documentDnsDidSigned); + expect(didFragments).toMatchInlineSnapshot(` + Array [ + Object { + "data": true, + "name": "OpenAttestationHash", + "status": "VALID", + "type": "DOCUMENT_INTEGRITY", + }, + Object { + "name": "OpenAttestationEthereumTokenRegistryStatus", + "reason": Object { + "code": 4, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"tokenRegistry\\" property or TOKEN_REGISTRY method", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "name": "OpenAttestationEthereumDocumentStoreStatus", + "reason": Object { + "code": 4, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"documentStore\\" or \\"certificateStore\\" property or DOCUMENT_STORE method", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "data": Object { + "details": Object { + "issuance": Array [ + Object { + "did": "did:ethr:0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89", + "issued": true, + }, + ], + "revocation": Array [ + Object { + "revoked": false, + }, + ], + }, + "issuedOnAll": true, + "revokedOnAny": false, + }, + "name": "OpenAttestationDidSignedDocumentStatus", + "status": "VALID", + "type": "DOCUMENT_STATUS", + }, + Object { + "name": "OpenAttestationDnsTxtIdentityProof", + "reason": Object { + "code": 2, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"documentStore\\" / \\"tokenRegistry\\" property or doesn't use DNS-TXT type", + }, + "status": "SKIPPED", + "type": "ISSUER_IDENTITY", + }, + Object { + "name": "OpenAttestationDnsDidIdentityProof", + "reason": Object { + "code": 0, + "codeString": "SKIPPED", + "message": "Document was not issued using DNS-DID", + }, + "status": "SKIPPED", + "type": "ISSUER_IDENTITY", + }, + ] + `); + expect(dnsDidFragments).toMatchInlineSnapshot(` + Array [ + Object { + "data": true, + "name": "OpenAttestationHash", + "status": "VALID", + "type": "DOCUMENT_INTEGRITY", + }, + Object { + "name": "OpenAttestationEthereumTokenRegistryStatus", + "reason": Object { + "code": 4, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"tokenRegistry\\" property or TOKEN_REGISTRY method", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "name": "OpenAttestationEthereumDocumentStoreStatus", + "reason": Object { + "code": 4, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"documentStore\\" or \\"certificateStore\\" property or DOCUMENT_STORE method", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "data": Object { + "details": Object { + "issuance": Array [ + Object { + "did": "did:ethr:0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89", + "issued": true, + }, + ], + "revocation": Array [ + Object { + "revoked": false, + }, + ], + }, + "issuedOnAll": true, + "revokedOnAny": false, + }, + "name": "OpenAttestationDidSignedDocumentStatus", + "status": "VALID", + "type": "DOCUMENT_STATUS", + }, + Object { + "name": "OpenAttestationDnsTxtIdentityProof", + "reason": Object { + "code": 2, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"documentStore\\" / \\"tokenRegistry\\" property or doesn't use DNS-TXT type", + }, + "status": "SKIPPED", + "type": "ISSUER_IDENTITY", + }, + Object { + "data": Array [ + Object { + "key": "did:ethr:0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89#controller", + "location": "example.tradetrust.io", + "status": "VALID", + }, + ], + "name": "OpenAttestationDnsDidIdentityProof", + "status": "VALID", + "type": "ISSUER_IDENTITY", + }, + ] + `); + }, 10000); }); diff --git a/src/verify.v3.integration.test.ts b/src/verify.v3.integration.test.ts index 5a550abc..cf2b5e3a 100644 --- a/src/verify.v3.integration.test.ts +++ b/src/verify.v3.integration.test.ts @@ -49,6 +49,25 @@ const verifyRopsten = verificationBuilder([...openAttestationVerifiers, openAtte }); describe("verify v3(integration)", () => { + beforeEach(() => { + jest.resetModules(); + process.env = Object.assign(process.env, { + PROVIDER_NETWORK: "", + PROVIDER_API_KEY: "", + PROVIDER_ENDPOINT_TYPE: "", + PROVIDER_ENDPOINT_URL: "", + }); + // eslint-disable-next-line @typescript-eslint/no-empty-function + jest.spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + delete process.env.PROVIDER_NETWORK; + delete process.env.PROVIDER_API_KEY; + delete process.env.PROVIDER_ENDPOINT_TYPE; + delete process.env.PROVIDER_ENDPOINT_URL; + jest.spyOn(console, "warn").mockRestore(); + }); describe("valid documents", () => { it("should return valid fragments for document issued correctly with DID & using DID identity proof", async () => { const fragments = await verifyRopsten(v3DidSigned); @@ -131,7 +150,7 @@ describe("verify v3(integration)", () => { ] `); expect(valid).toBe(true); - }); + }, 10000); it("should return valid fragments for document issued correctly with DID & using DID identity proof, but not revoked on a document store", async () => { const fragments = await verifyRopsten(v3DidSignedRevocationStoreNotRevoked); const valid = isValid(fragments); @@ -214,7 +233,7 @@ describe("verify v3(integration)", () => { ] `); expect(valid).toBe(true); - }); + }, 10000); it("should return valid fragments for document issued correctly with DID & using DNS-DID identity proof", async () => { const fragments = await verifyRopsten(v3DnsDidSigned); const valid = isValid(fragments); @@ -297,7 +316,7 @@ describe("verify v3(integration)", () => { ] `); expect(valid).toBe(true); - }); + }, 10000); it("should return valid fragments for document issued correctly with DID & using DNS-DID identity proof, but not revoked on a document store", async () => { const fragments = await verifyRopsten(v3DnsDidSignedRevocationStoreNotRevoked); const valid = isValid(fragments); @@ -381,7 +400,7 @@ describe("verify v3(integration)", () => { ] `); expect(valid).toBe(true); - }); + }, 10000); it("should return valid fragments for document issued correctly with document store & using DNS-TXT identity proof", async () => { const fragments = await verifyRopsten(v3DocumentStoreIssued); const valid = isValid(fragments); @@ -464,7 +483,7 @@ describe("verify v3(integration)", () => { ] `); expect(valid).toBe(true); - }); + }, 10000); it("should return valid fragments for document issued correctly with token registry & using DNS-TXT identity proof", async () => { const fragments = await verifyRopsten(v3TokenRegistryIssued); const valid = isValid(fragments); @@ -540,7 +559,7 @@ describe("verify v3(integration)", () => { ] `); expect(valid).toBe(true); - }); + }, 10000); it("should return valid fragments for documents correctly issued but with data obfuscated", async () => { const obfuscated = obfuscate(v3DidSigned, ["reference"]); expect(obfuscated.reference).toBe(undefined); @@ -624,7 +643,328 @@ describe("verify v3(integration)", () => { ] `); expect(valid).toBe(true); - }); + }, 10000); + + it("should return the correct fragments even when process.env is used for out of the box verify for document with document store", async () => { + // simulate loading process.env from .env file + process.env.PROVIDER_NETWORK = "ropsten"; + process.env.PROVIDER_ENDPOINT_TYPE = "alchemy"; + const defaultBuilderOption = { + network: process.env.PROVIDER_NETWORK || "homestead", + }; + const verification = verificationBuilder(openAttestationVerifiers, defaultBuilderOption); + const fragments = await verification(v3DocumentStoreIssued); + expect(fragments).toMatchInlineSnapshot(` + Array [ + Object { + "data": true, + "name": "OpenAttestationHash", + "status": "VALID", + "type": "DOCUMENT_INTEGRITY", + }, + Object { + "name": "OpenAttestationEthereumTokenRegistryStatus", + "reason": Object { + "code": 4, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"tokenRegistry\\" property or TOKEN_REGISTRY method", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "data": Object { + "details": Object { + "issuance": Object { + "address": "0x8bA63EAB43342AAc3AdBB4B827b68Cf4aAE5Caca", + "issued": true, + }, + "revocation": Object { + "address": "0x8bA63EAB43342AAc3AdBB4B827b68Cf4aAE5Caca", + "revoked": false, + }, + }, + "issuedOnAll": true, + "revokedOnAny": false, + }, + "name": "OpenAttestationEthereumDocumentStoreStatus", + "status": "VALID", + "type": "DOCUMENT_STATUS", + }, + Object { + "name": "OpenAttestationDidSignedDocumentStatus", + "reason": Object { + "code": 0, + "codeString": "SKIPPED", + "message": "Document was not signed by DID directly", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "data": Object { + "identifier": "example.tradetrust.io", + "value": "0x8bA63EAB43342AAc3AdBB4B827b68Cf4aAE5Caca", + }, + "name": "OpenAttestationDnsTxtIdentityProof", + "status": "VALID", + "type": "ISSUER_IDENTITY", + }, + Object { + "name": "OpenAttestationDnsDidIdentityProof", + "reason": Object { + "code": 0, + "codeString": "SKIPPED", + "message": "Document was not issued using DNS-DID", + }, + "status": "SKIPPED", + "type": "ISSUER_IDENTITY", + }, + ] + `); + }, 10000); + it("should use the defaults to connect to provider even when process.env is not there for document with document store", async () => { + const defaultBuilderOption = { + network: process.env.PROVIDER_NETWORK || "homestead", + }; + const verification = verificationBuilder(openAttestationVerifiers, defaultBuilderOption); + const fragments = await verification(v3DocumentStoreIssued); + expect(fragments).toMatchInlineSnapshot(` + Array [ + Object { + "data": true, + "name": "OpenAttestationHash", + "status": "VALID", + "type": "DOCUMENT_INTEGRITY", + }, + Object { + "name": "OpenAttestationEthereumTokenRegistryStatus", + "reason": Object { + "code": 4, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"tokenRegistry\\" property or TOKEN_REGISTRY method", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "data": Object { + "details": Object { + "issuance": Object { + "address": "0x8bA63EAB43342AAc3AdBB4B827b68Cf4aAE5Caca", + "issued": false, + "reason": Object { + "code": 1, + "codeString": "DOCUMENT_NOT_ISSUED", + "message": "Contract is not found", + }, + }, + "revocation": Object { + "address": "0x8bA63EAB43342AAc3AdBB4B827b68Cf4aAE5Caca", + "reason": Object { + "code": 5, + "codeString": "DOCUMENT_REVOKED", + "message": "Contract is not found", + }, + "revoked": true, + }, + }, + "issuedOnAll": false, + "revokedOnAny": true, + }, + "name": "OpenAttestationEthereumDocumentStoreStatus", + "reason": Object { + "code": 5, + "codeString": "DOCUMENT_REVOKED", + "message": "Contract is not found", + }, + "status": "INVALID", + "type": "DOCUMENT_STATUS", + }, + Object { + "name": "OpenAttestationDidSignedDocumentStatus", + "reason": Object { + "code": 0, + "codeString": "SKIPPED", + "message": "Document was not signed by DID directly", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "data": Object { + "identifier": "example.tradetrust.io", + "value": "0x8bA63EAB43342AAc3AdBB4B827b68Cf4aAE5Caca", + }, + "name": "OpenAttestationDnsTxtIdentityProof", + "reason": Object { + "code": 4, + "codeString": "MATCHING_RECORD_NOT_FOUND", + "message": "Matching DNS record not found for 0x8bA63EAB43342AAc3AdBB4B827b68Cf4aAE5Caca", + }, + "status": "INVALID", + "type": "ISSUER_IDENTITY", + }, + Object { + "name": "OpenAttestationDnsDidIdentityProof", + "reason": Object { + "code": 0, + "codeString": "SKIPPED", + "message": "Document was not issued using DNS-DID", + }, + "status": "SKIPPED", + "type": "ISSUER_IDENTITY", + }, + ] + `); + }, 10000); + it("should return the correct fragments when using process.env variables for did resolver", async () => { + // simulate loading process.env from .env file + process.env.PROVIDER_NETWORK = "ropsten"; + process.env.PROVIDER_ENDPOINT_TYPE = "alchemy"; + const defaultBuilderOption = { + network: process.env.PROVIDER_NETWORK || "homestead", + }; + const verification = verificationBuilder(openAttestationVerifiers, defaultBuilderOption); + const didFragments = await verification(v3DidSigned); + const dnsDidFragments = await verification(v3DnsDidSigned); + expect(didFragments).toMatchInlineSnapshot(` + Array [ + Object { + "data": true, + "name": "OpenAttestationHash", + "status": "VALID", + "type": "DOCUMENT_INTEGRITY", + }, + Object { + "name": "OpenAttestationEthereumTokenRegistryStatus", + "reason": Object { + "code": 4, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"tokenRegistry\\" property or TOKEN_REGISTRY method", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "name": "OpenAttestationEthereumDocumentStoreStatus", + "reason": Object { + "code": 4, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"documentStore\\" or \\"certificateStore\\" property or DOCUMENT_STORE method", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "data": Object { + "details": Object { + "issuance": Object { + "did": "did:ethr:0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89", + "issued": true, + }, + "revocation": Object { + "revoked": false, + }, + }, + "issuedOnAll": true, + "revokedOnAny": false, + }, + "name": "OpenAttestationDidSignedDocumentStatus", + "status": "VALID", + "type": "DOCUMENT_STATUS", + }, + Object { + "name": "OpenAttestationDnsTxtIdentityProof", + "reason": Object { + "code": 2, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"documentStore\\" / \\"tokenRegistry\\" property or doesn't use DNS-TXT type", + }, + "status": "SKIPPED", + "type": "ISSUER_IDENTITY", + }, + Object { + "name": "OpenAttestationDnsDidIdentityProof", + "reason": Object { + "code": 0, + "codeString": "SKIPPED", + "message": "Document was not issued using DNS-DID", + }, + "status": "SKIPPED", + "type": "ISSUER_IDENTITY", + }, + ] + `); + expect(dnsDidFragments).toMatchInlineSnapshot(` + Array [ + Object { + "data": true, + "name": "OpenAttestationHash", + "status": "VALID", + "type": "DOCUMENT_INTEGRITY", + }, + Object { + "name": "OpenAttestationEthereumTokenRegistryStatus", + "reason": Object { + "code": 4, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"tokenRegistry\\" property or TOKEN_REGISTRY method", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "name": "OpenAttestationEthereumDocumentStoreStatus", + "reason": Object { + "code": 4, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"documentStore\\" or \\"certificateStore\\" property or DOCUMENT_STORE method", + }, + "status": "SKIPPED", + "type": "DOCUMENT_STATUS", + }, + Object { + "data": Object { + "details": Object { + "issuance": Object { + "did": "did:ethr:0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89", + "issued": true, + }, + "revocation": Object { + "revoked": false, + }, + }, + "issuedOnAll": true, + "revokedOnAny": false, + }, + "name": "OpenAttestationDidSignedDocumentStatus", + "status": "VALID", + "type": "DOCUMENT_STATUS", + }, + Object { + "name": "OpenAttestationDnsTxtIdentityProof", + "reason": Object { + "code": 2, + "codeString": "SKIPPED", + "message": "Document issuers doesn't have \\"documentStore\\" / \\"tokenRegistry\\" property or doesn't use DNS-TXT type", + }, + "status": "SKIPPED", + "type": "ISSUER_IDENTITY", + }, + Object { + "data": Object { + "key": "did:ethr:0xE712878f6E8d5d4F9e87E10DA604F9cB564C9a89#controller", + "location": "example.tradetrust.io", + "status": "VALID", + }, + "name": "OpenAttestationDnsDidIdentityProof", + "status": "VALID", + "type": "ISSUER_IDENTITY", + }, + ] + `); + }, 10000); }); describe("invalid documents", () => { describe("document store", () => { @@ -694,7 +1034,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); it("should return invalid fragments for document that has been revoked", async () => { const fragments = await verifyRopsten(v3DocumentStoreRevoked); expect(isValid(fragments, ["DOCUMENT_STATUS"])).toStrictEqual(false); @@ -757,7 +1097,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); }); describe("token registry", () => { it("should return invalid fragments for documents that has been tampered", async () => { @@ -787,7 +1127,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); it("should return invalid fragments for document that has not been issued", async () => { const fragments = await verifyRopsten(v3TokenRegistryWrapped); expect(isValid(fragments, ["DOCUMENT_STATUS"])).toStrictEqual(false); @@ -819,7 +1159,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); it("should return invalid fragments for documents that is using invalid DNS but correctly issued", async () => { const fragments = await verifyRopsten(v3TokenRegistryInvalidIssued); expect(isValid(fragments, ["DOCUMENT_STATUS"])).toStrictEqual(true); @@ -843,7 +1183,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); }); describe("did (DNS-DID)", () => { it("should return invalid fragments for documents that has been tampered", async () => { @@ -873,7 +1213,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); it("should return skipped fragments for documents that has not been signed", async () => { const fragments = await verifyRopsten(v3DnsDidWrapped); expect(isValid(fragments, ["DOCUMENT_STATUS"])).toStrictEqual(false); @@ -893,7 +1233,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); it("should return invalid fragments for documents with revocation method obfuscated", async () => { const obfuscated = obfuscate(v3DnsDidSigned, ["openAttestationMetadata.proof.revocation"]); const fragments = await verifyRopsten(obfuscated); @@ -915,7 +1255,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); it("should return invalid fragments for documents that is using invalid DNS but correctly signed", async () => { const fragments = await verifyRopsten(v3DnsDidInvalidSigned); expect(isValid(fragments, ["DOCUMENT_STATUS"])).toStrictEqual(true); @@ -940,7 +1280,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); it("should return invalid fragments for documents that have been revoked", async () => { const fragments = await verifyRopsten(v3DnsDidSignedRevocationStoreButRevoked); expect(isValid(fragments, ["DOCUMENT_STATUS"])).toStrictEqual(false); @@ -979,7 +1319,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); }); describe("did (DID)", () => { it("should return invalid fragments for documents that has been tampered", async () => { @@ -1009,7 +1349,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); it("should return invalid fragments for documents that has not been signed", async () => { const fragments = await verifyRopsten(v3DidWrapped); expect(isValid(fragments, ["DOCUMENT_STATUS"])).toStrictEqual(false); @@ -1030,7 +1370,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); it("should return invalid fragments for documents with revocation method obfuscated", async () => { const obfuscated = obfuscate(v3DidSigned, ["openAttestationMetadata.proof.revocation"]); const fragments = await verifyRopsten(obfuscated); @@ -1052,7 +1392,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); it("should return invalid fragments for documents that is using invalid DNS but correctly signed", async () => { const fragments = await verifyRopsten(v3DidInvalidSigned); expect(isValid(fragments, ["DOCUMENT_STATUS"])).toStrictEqual(true); @@ -1077,7 +1417,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); it("should return invalid fragments for documents that have been revoked", async () => { const fragments = await verifyRopsten(v3DidSignedRevocationStoreButRevoked); expect(isValid(fragments, ["DOCUMENT_STATUS"])).toStrictEqual(false); @@ -1116,7 +1456,7 @@ describe("verify v3(integration)", () => { }, ] `); - }); + }, 10000); }); }); });