From 2afb30d3e47801786bc6bb94be69e08da617e62c Mon Sep 17 00:00:00 2001 From: Yarmo Mackenbach Date: Fri, 16 Apr 2021 10:56:33 +0200 Subject: [PATCH] Deprecate claims.js, introduce proofs.js --- src/claims.js | 337 -------------------------------------------------- src/index.js | 2 +- src/proofs.js | 170 +++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 338 deletions(-) delete mode 100644 src/claims.js create mode 100644 src/proofs.js diff --git a/src/claims.js b/src/claims.js deleted file mode 100644 index 5449ba9..0000000 --- a/src/claims.js +++ /dev/null @@ -1,337 +0,0 @@ -/* -Copyright 2021 Yarmo Mackenbach - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -const mergeOptions = require('merge-options') -const validUrl = require('valid-url') -const openpgp = require('openpgp') -const serviceproviders = require('./serviceproviders') -const keys = require('./keys') -const utils = require('./utils') - -// Promise.allSettled polyfill -Promise.allSettled = - Promise.allSettled || - ((promises) => - Promise.all( - promises.map((p) => - p - .then((v) => ({ - status: 'fulfilled', - value: v, - })) - .catch((e) => ({ - status: 'rejected', - reason: e, - })) - ) - )) - -const runVerificationJson = ( - res, - proofData, - checkPath, - checkClaim, - checkRelation -) => { - let re - - if (res.isVerified || !proofData) { - return res - } - - if (Array.isArray(proofData)) { - proofData.forEach((item, i) => { - res = runVerificationJson(res, item, checkPath, checkClaim, checkRelation) - }) - return res - } - - if (checkPath.length == 0) { - switch (checkRelation) { - default: - case E.ClaimRelation.CONTAINS: - re = new RegExp(checkClaim, 'gi') - res.isVerified = re.test(proofData.replace(/\r?\n|\r|\\/g, '')) - break - case E.ClaimRelation.EQUALS: - res.isVerified = - proofData.replace(/\r?\n|\r|\\/g, '').toLowerCase() == - checkClaim.toLowerCase() - break - case E.ClaimRelation.ONEOF: - re = new RegExp(checkClaim, 'gi') - res.isVerified = re.test(proofData.join('|')) - break - } - return res - } - - try { - checkPath[0] in proofData - } catch (e) { - res.errors.push('err_data_structure_incorrect') - return res - } - - res = runVerificationJson( - res, - proofData[checkPath[0]], - checkPath.slice(1), - checkClaim, - checkRelation - ) - return res -} - -const runVerification = (proofData, spData) => { - let res = { - isVerified: false, - errors: [], - } - - switch (spData.proof.format) { - case E.ProofFormat.JSON: - res = runVerificationJson( - res, - proofData, - spData.claim.path, - utils.generateClaim(spData.claim.fingerprint, spData.claim.format), - spData.claim.relation - ) - break - case E.ProofFormat.TEXT: - re = new RegExp( - utils - .generateClaim(spData.claim.fingerprint, spData.claim.format) - .replace('[', '\\[') - .replace(']', '\\]'), - 'gi' - ) - res.isVerified = re.test(proofData.replace(/\r?\n|\r/, '')) - break - } - - return res -} - -const verify = async (input, fingerprint, opts) => { - if (input instanceof openpgp.key.Key) { - const fingerprintFromKey = await keys.getFingerprint(input) - const userData = await keys.getUserData(input) - - const promises = userData.map(async (user, i) => { - return new Promise(async (resolve, reject) => { - try { - const res = await verify(user.notations, fingerprintFromKey, opts) - resolve(res) - } catch (e) { - reject(e) - } - }) - }) - - return Promise.allSettled(promises).then((values) => { - return values.map((obj, i) => { - if (obj.status == 'fulfilled') { - return obj.value - } else { - return obj.reason - } - }) - }) - } - if (input instanceof Array) { - const promises = input.map(async (uri, i) => { - return new Promise(async (resolve, reject) => { - try { - const res = await verify(uri, fingerprint, opts) - resolve(res) - } catch (e) { - reject(e) - } - }) - }) - - return Promise.allSettled(promises).then((values) => { - return values.map((obj, i) => { - if (obj.status == 'fulfilled') { - return obj.value - } else { - return obj.reason - } - }) - }) - } - - const promiseClaim = new Promise(async (resolve, reject) => { - let objResult = { - isVerified: false, - errors: [], - serviceproviderData: undefined, - } - - const uri = input.replace(/^\s+|\s+$/g, '') - - if (!fingerprint) { - fingerprint = null - } - - const defaultOpts = { - returnMatchesOnly: false, - proxyPolicy: 'adaptive', - doipProxyHostname: 'proxy.keyoxide.org', - twitterBearerToken: null, - nitterInstance: null, - } - opts = mergeOptions(defaultOpts, opts ? opts : {}) - - if (!validUrl.isUri(uri)) { - objResult.errors.push('invalid_uri') - reject(objResult) - return - } - - const spMatches = serviceproviders.match(uri, opts) - - if ('returnMatchesOnly' in opts && opts.returnMatchesOnly) { - resolve(spMatches) - return - } - - let claimVerificationDone = false, - claimVerificationResult, - sp, - iSp = 0, - res, - proofData, - spData - - while (!claimVerificationDone && iSp < spMatches.length) { - spData = spMatches[iSp] - spData.claim.fingerprint = fingerprint - - res = null - - if (spData.customRequestHandler instanceof Function) { - try { - proofData = await spData.customRequestHandler(spData, opts) - } catch (e) { - objResult.errors.push('custom_request_handler_failed') - } - } else { - switch (opts.proxyPolicy) { - case 'adaptive': - if (spData.proof.useProxy) { - try { - proofData = await serviceproviders.proxyRequestHandler( - spData, - opts - ) - } catch (er) {} - } else { - try { - proofData = await serviceproviders.directRequestHandler( - spData, - opts - ) - } catch (er) {} - if (!proofData) { - try { - proofData = await serviceproviders.proxyRequestHandler( - spData, - opts - ) - } catch (er) {} - } - } - break - case 'fallback': - try { - proofData = await serviceproviders.directRequestHandler( - spData, - opts - ) - } catch (er) {} - if (!proofData) { - try { - proofData = await serviceproviders.proxyRequestHandler( - spData, - opts - ) - } catch (er) {} - } - break - case 'always': - try { - proofData = await serviceproviders.proxyRequestHandler( - spData, - opts - ) - } catch (er) {} - break - case 'never': - try { - proofData = await serviceproviders.directRequestHandler( - spData, - opts - ) - } catch (er) {} - break - default: - objResult.errors.push('invalid_proxy_policy') - } - } - - if (proofData) { - claimVerificationResult = runVerification(proofData, spData) - - if (claimVerificationResult.errors.length == 0) { - claimVerificationDone = true - } - } else { - objResult.errors.push('unsuccessful_claim_verification') - } - - iSp++ - } - - if (!claimVerificationResult) { - claimVerificationResult = { - isVerified: false, - } - } - - objResult.isVerified = claimVerificationResult.isVerified - objResult.serviceproviderData = spData - resolve(objResult) - return - }) - - const promiseTimeout = new Promise((resolve) => { - const objResult = { - isVerified: false, - errors: ['verification_timed_out'], - serviceproviderData: undefined, - } - setTimeout(() => { - resolve(objResult) - return - }, 10000) - }) - - return await Promise.race([promiseClaim, promiseTimeout]) -} - -exports.verify = verify diff --git a/src/index.js b/src/index.js index d4dc050..defffad 100644 --- a/src/index.js +++ b/src/index.js @@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -const claims = require('./claims') +const proofs = require('./proofs') const keys = require('./keys') const signatures = require('./signatures') const serviceproviders = require('./serviceproviders') diff --git a/src/proofs.js b/src/proofs.js new file mode 100644 index 0000000..d96cb49 --- /dev/null +++ b/src/proofs.js @@ -0,0 +1,170 @@ +/* +Copyright 2021 Yarmo Mackenbach + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +const jsEnv = require('browser-or-node') +const fetcher = require('./fetcher') +const utils = require('./utils') +const E = require('./enums') + +const fetch = (data, opts) => { + switch (data.proof.request.fetcher) { + case E.Fetcher.HTTP: + data.proof.request.data.format = data.proof.request.format + break + + default: + break + } + + if (jsEnv.isNode) { + return handleNodeRequests(data, opts) + } + + return handleBrowserRequests(data, opts) +} + +const handleBrowserRequests = (data, opts) => { + switch (opts.proxy.policy) { + case E.ProxyPolicy.ALWAYS: + return createProxyRequestPromise(data, opts) + break + + case E.ProxyPolicy.NEVER: + switch (data.proof.request.access) { + case E.ProofAccess.GENERIC: + case E.ProofAccess.GRANTED: + return createDefaultRequestPromise(data, opts) + break + case E.ProofAccess.NOCORS: + case E.ProofAccess.SERVER: + throw new Error('Impossible to fetch proof (bad combination of service access and proxy policy)') + break + default: + throw new Error('Invalid proof access value') + break + } + break + + case E.ProxyPolicy.ADAPTIVE: + switch (data.proof.request.access) { + case E.ProofAccess.GENERIC: + return createDefaultRequestPromise(data, opts) + break + case E.ProofAccess.NOCORS: + return createProxyRequestPromise(data, opts) + break + case E.ProofAccess.GRANTED: + return createFallbackRequestPromise(data, opts) + break + case E.ProofAccess.SERVER: + throw new Error('Impossible to fetch proof (bad combination of service access and proxy policy)') + break + default: + throw new Error('Invalid proof access value') + break + } + break + + default: + throw new Error('Invalid proxy policy') + break + } +} + +const handleNodeRequests = (data, opts) => { + switch (opts.proxy.policy) { + case E.ProxyPolicy.ALWAYS: + return createProxyRequestPromise(data, opts) + break + + case E.ProxyPolicy.NEVER: + return createDefaultRequestPromise(data, opts) + break + + case E.ProxyPolicy.ADAPTIVE: + return createFallbackRequestPromise(data, opts) + break + + default: + throw new Error('Invalid proxy policy') + break + } +} + +const createDefaultRequestPromise = (data, opts) => { + return new Promise((resolve, reject) => { + fetcher[data.proof.request.fetcher].fn(data.proof.request.data, opts) + .then(res => { + return resolve({ + fetcher: data.proof.request.fetcher, + data: data, + viaProxy: false, + result: res + }) + }) + .catch(err => { + return reject(err) + }) + }) +} + +const createProxyRequestPromise = (data, opts) => { + return new Promise((resolve, reject) => { + let proxyUrl + try { + proxyUrl = utils.generateProxyURL(data.proof.request.fetcher, data.proof.request.data, opts); + } catch (err) { + reject(err) + } + + const requestData = { + url: proxyUrl, + format: data.proof.request.format, + fetcherTimeout: fetcher[data.proof.request.fetcher].timeout + } + fetcher.http.fn(requestData, opts) + .then(res => { + return resolve({ + fetcher: 'http', + data: data, + viaProxy: true, + result: res + }) + }) + .catch(err => { + return reject(err) + }) + }) +} + +const createFallbackRequestPromise = (data, opts) => { + return new Promise((resolve, reject) => { + createDefaultRequestPromise(data, opts) + .then(res => { + return resolve(res) + }) + .catch(err1 => { + createProxyRequestPromise(data, opts) + .then(res => { + return resolve(res) + }) + .catch(err2 => { + return reject([err1, err2]) + }) + }) + }) +} + +exports.fetch = fetch \ No newline at end of file