From 870a5445505975462fc2b4ca1b2f50b4445cc29a Mon Sep 17 00:00:00 2001 From: Yarmo Mackenbach Date: Wed, 29 Mar 2023 13:04:56 +0200 Subject: [PATCH] feat: Add marker fetching logic --- src/claim.js | 38 +++++++++++- src/enums.js | 15 +++++ src/index.js | 4 +- src/{proofs.js => request.js} | 110 ++++++++++++++++++++++++---------- test/claimDefinitions.test.js | 1 + 5 files changed, 131 insertions(+), 37 deletions(-) rename src/{proofs.js => request.js} (54%) diff --git a/src/claim.js b/src/claim.js index ab5c030..38666c6 100644 --- a/src/claim.js +++ b/src/claim.js @@ -16,7 +16,7 @@ limitations under the License. const validator = require('validator') const validUrl = require('valid-url') const mergeOptions = require('merge-options') -const proofs = require('./proofs') +const request = require('./request') const verifications = require('./verifications') const claimDefinitions = require('./claimDefinitions') const defaults = require('./defaults') @@ -229,10 +229,44 @@ class Claim { let verificationResult = null let proofData = null + let markersData = null let proofFetchError + // Handle markers try { - proofData = await proofs.fetch(claimData, opts) + markersData = await request.fetchMarkers(claimData, opts) + } catch (err) { + proofFetchError = err + } + if (markersData) { + let shouldSkipMatch = false + markersData.forEach(marker => { + // Skip marker if another was already proven false + if (shouldSkipMatch) return + + // Ignore markers that were rejected + if (marker.status !== 'fulfilled') return + + let endpointExists + switch (marker.value.data.test.type) { + case E.MarkerTestType.HTTP_ENDPOINT_MUST_EXIST: + endpointExists = marker.value.result && !marker.value.error + if ((endpointExists && marker.value.data.test.inverse) || (!(endpointExists || marker.value.data.test.inverse))) { + shouldSkipMatch = true + } + break + + default: + break + } + }) + + if (shouldSkipMatch) continue + } + + // Handle proof + try { + proofData = await request.fetchProof(claimData, opts) } catch (err) { proofFetchError = err } diff --git a/src/enums.js b/src/enums.js index f36155f..7e012c6 100644 --- a/src/enums.js +++ b/src/enums.js @@ -146,6 +146,20 @@ const ClaimStatus = { } Object.freeze(ClaimStatus) +/** + * How to test a marker + * @readonly + * @enum {string} + */ +const MarkerTestType = { + /** HTTP endpoint must exist */ + HTTP_ENDPOINT_MUST_EXIST: 'httpEndpointMustExist' + // TODO Implement JSON_CONTAINS + // /** JSON data must contain a certain string */ + // JSON_CONTAINS: 'jsonContains' +} +Object.freeze(MarkerTestType) + exports.ProxyPolicy = ProxyPolicy exports.Fetcher = Fetcher exports.EntityEncodingFormat = EntityEncodingFormat @@ -154,3 +168,4 @@ exports.ProofFormat = ProofFormat exports.ClaimFormat = ClaimFormat exports.ClaimRelation = ClaimRelation exports.ClaimStatus = ClaimStatus +exports.MarkerTestType = MarkerTestType diff --git a/src/index.js b/src/index.js index a647b7f..cebeafd 100644 --- a/src/index.js +++ b/src/index.js @@ -15,7 +15,7 @@ limitations under the License. */ const Claim = require('./claim') const claimDefinitions = require('./claimDefinitions') -const proofs = require('./proofs') +const request = require('./request') const keys = require('./keys') const signatures = require('./signatures') const enums = require('./enums') @@ -26,7 +26,7 @@ const fetcher = require('./fetcher') exports.Claim = Claim exports.claimDefinitions = claimDefinitions -exports.proofs = proofs +exports.request = request exports.keys = keys exports.signatures = signatures exports.enums = enums diff --git a/src/proofs.js b/src/request.js similarity index 54% rename from src/proofs.js rename to src/request.js index 9f824e9..c765e2b 100644 --- a/src/proofs.js +++ b/src/request.js @@ -19,7 +19,7 @@ const utils = require('./utils') const E = require('./enums') /** - * @module proofs + * @module request */ /** @@ -33,7 +33,7 @@ const E = require('./enums') * @param {object} opts - Options to enable the request * @returns {Promise} */ -const fetch = (data, opts) => { +const fetchProof = (data, opts) => { switch (data.proof.request.fetcher) { case E.Fetcher.HTTP: data.proof.request.data.format = data.proof.request.format @@ -44,22 +44,49 @@ const fetch = (data, opts) => { } if (jsEnv.isNode) { - return handleNodeRequests(data, opts) + return handleNodeRequests(data.proof, opts, false) } - return handleBrowserRequests(data, opts) + return handleBrowserRequests(data.proof, opts, false) } -const handleBrowserRequests = (data, opts) => { +/** + * Delegate the marker requests to the correct fetcher. + * This method uses the current environment (browser/node), certain values from + * the `data` parameter and the proxy policy set in the `opts` parameter to + * choose the right approach to fetch the proof. An error will be thrown if no + * approach is possible. + * @async + * @param {object} data - Data from a claim definition + * @param {object} opts - Options to enable the request + * @returns {Promise>} + */ +const fetchMarkers = async (data, opts) => { + const promises = [] + + if (!(data.markers && data.markers.length > 0)) throw new Error('No markers found') + + data.markers.forEach(marker => { + if (jsEnv.isNode) { + promises.push(handleNodeRequests(marker, opts, true)) + } else { + promises.push(handleBrowserRequests(marker, opts, true)) + } + }) + + return Promise.allSettled(promises) +} + +const handleBrowserRequests = (data, opts, alwaysResolve) => { switch (opts.proxy.policy) { case E.ProxyPolicy.ALWAYS: - return createProxyRequestPromise(data, opts) + return createProxyRequestPromise(data, opts, alwaysResolve) case E.ProxyPolicy.NEVER: - switch (data.proof.request.access) { + switch (data.request.access) { case E.ProofAccess.GENERIC: case E.ProofAccess.GRANTED: - return createDefaultRequestPromise(data, opts) + return createDefaultRequestPromise(data, opts, alwaysResolve) case E.ProofAccess.NOCORS: case E.ProofAccess.SERVER: throw new Error( @@ -70,15 +97,13 @@ const handleBrowserRequests = (data, opts) => { } case E.ProxyPolicy.ADAPTIVE: - switch (data.proof.request.access) { + switch (data.request.access) { case E.ProofAccess.GENERIC: - return createFallbackRequestPromise(data, opts) - case E.ProofAccess.NOCORS: - return createProxyRequestPromise(data, opts) case E.ProofAccess.GRANTED: - return createFallbackRequestPromise(data, opts) + return createFallbackRequestPromise(data, opts, alwaysResolve) + case E.ProofAccess.NOCORS: case E.ProofAccess.SERVER: - return createProxyRequestPromise(data, opts) + return createProxyRequestPromise(data, opts, alwaysResolve) default: throw new Error('Invalid proof access value') } @@ -88,47 +113,56 @@ const handleBrowserRequests = (data, opts) => { } } -const handleNodeRequests = (data, opts) => { +const handleNodeRequests = (data, opts, alwaysResolve) => { switch (opts.proxy.policy) { case E.ProxyPolicy.ALWAYS: - return createProxyRequestPromise(data, opts) + return createProxyRequestPromise(data, opts, alwaysResolve) case E.ProxyPolicy.NEVER: - return createDefaultRequestPromise(data, opts) + return createDefaultRequestPromise(data, opts, alwaysResolve) case E.ProxyPolicy.ADAPTIVE: - return createFallbackRequestPromise(data, opts) + return createFallbackRequestPromise(data, opts, alwaysResolve) default: throw new Error('Invalid proxy policy') } } -const createDefaultRequestPromise = (data, opts) => { +const createDefaultRequestPromise = (data, opts, alwaysResolve) => { return new Promise((resolve, reject) => { - fetcher[data.proof.request.fetcher] - .fn(data.proof.request.data, opts) + fetcher[data.request.fetcher] + .fn(data.request.data, opts) .then((res) => { return resolve({ - fetcher: data.proof.request.fetcher, + fetcher: data.request.fetcher, data: data, viaProxy: false, result: res }) }) .catch((err) => { - return reject(err) + if (alwaysResolve) { + return resolve({ + fetcher: 'http', + data: data, + viaProxy: true, + error: err + }) + } else { + return reject(err) + } }) }) } -const createProxyRequestPromise = (data, opts) => { +const createProxyRequestPromise = (data, opts, alwaysResolve) => { return new Promise((resolve, reject) => { let proxyUrl try { proxyUrl = utils.generateProxyURL( - data.proof.request.fetcher, - data.proof.request.data, + data.request.fetcher, + data.request.data, opts ) } catch (err) { @@ -137,8 +171,8 @@ const createProxyRequestPromise = (data, opts) => { const requestData = { url: proxyUrl, - format: data.proof.request.format, - fetcherTimeout: fetcher[data.proof.request.fetcher].timeout + format: data.request.format, + fetcherTimeout: fetcher[data.request.fetcher].timeout } fetcher.http .fn(requestData, opts) @@ -151,19 +185,28 @@ const createProxyRequestPromise = (data, opts) => { }) }) .catch((err) => { - return reject(err) + if (alwaysResolve) { + return resolve({ + fetcher: 'http', + data: data, + viaProxy: true, + error: err + }) + } else { + return reject(err) + } }) }) } -const createFallbackRequestPromise = (data, opts) => { +const createFallbackRequestPromise = (data, opts, alwaysResolve) => { return new Promise((resolve, reject) => { - createDefaultRequestPromise(data, opts) + createDefaultRequestPromise(data, opts, alwaysResolve) .then((res) => { return resolve(res) }) .catch((err1) => { - createProxyRequestPromise(data, opts) + createProxyRequestPromise(data, opts, alwaysResolve) .then((res) => { return resolve(res) }) @@ -174,4 +217,5 @@ const createFallbackRequestPromise = (data, opts) => { }) } -exports.fetch = fetch +exports.fetchProof = fetchProof +exports.fetchMarkers = fetchMarkers diff --git a/test/claimDefinitions.test.js b/test/claimDefinitions.test.js index 222939b..ec16009 100644 --- a/test/claimDefinitions.test.js +++ b/test/claimDefinitions.test.js @@ -38,6 +38,7 @@ const pattern = { return _.isString(x) || _.isNull(x) }, }, + markers: _.isArray, proof: { uri: (x) => { return _.isString(x) || _.isNull(x)