Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: jsonld document loader node 18 #1454

2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"dependencies": {
"@digitalcredentials/jsonld": "^5.2.1",
"@digitalcredentials/jsonld-signatures": "^9.3.1",
"@digitalcredentials/vc": "^1.1.2",
"@digitalcredentials/vc": "^5.0.0",
"@multiformats/base-x": "^4.0.1",
"@stablelib/ed25519": "^1.0.2",
"@stablelib/random": "^1.0.1",
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/modules/vc/W3cCredentialService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,14 @@ export class W3cCredentialService {
verifyOptions['purpose'] = options.proofPurpose
}

const result = await vc.verifyCredential(verifyOptions)
const result = (await vc.verifyCredential(verifyOptions)) as unknown as W3cVerifyCredentialResult
if (!result.verified) {
agentContext.config.logger.debug(`Credential verification failed: ${result.error?.message}`, {
stack: result.error?.stack,
})
}

return result as unknown as W3cVerifyCredentialResult
return result
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { DocumentLoader } from './jsonld'

export function getNativeDocumentLoader(): () => DocumentLoader {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const loader = require('@digitalcredentials/jsonld/lib/documentLoaders/node')
const loader = require('./nodeDocumentLoader')

return loader as () => DocumentLoader
}
207 changes: 207 additions & 0 deletions packages/core/src/modules/vc/libraries/nodeDocumentLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// NOTE: this file is copied from the digitalcredentials/jsonld.js library
// as there's a bug in Node.JS 18. If the PR is merged and released we can remove
// the custom implementation in AFJ: https://github.com/digitalcredentials/jsonld.js/pull/2

// We ignore the typescript and eslint errors in this file, so we can keep the file as close to the original as possible
/* eslint-disable */
// @ts-nocheck

/*
* Copyright (c) 2017-2021 Digital Bazaar, Inc. All rights reserved.
*/

const { httpClient } = require('@digitalcredentials/http-client')
const https = require('https')

const JsonLdError = require('@digitalcredentials/jsonld/lib//JsonLdError')
const RequestQueue = require('@digitalcredentials/jsonld/lib//RequestQueue')
const { LINK_HEADER_CONTEXT } = require('@digitalcredentials/jsonld/lib//constants')
const { prependBase } = require('@digitalcredentials/jsonld/lib//url')
const { parseLinkHeader, buildHeaders } = require('@digitalcredentials/jsonld/lib//util')

/**
* Creates a built-in node document loader.
*
* @param options the options to use:
* [secure]: require all URLs to use HTTPS. (default: false)
* [strictSSL]: true to require SSL certificates to be valid,
* false not to. (default: true)
* [maxRedirects]: the maximum number of redirects to permit.
* (default: none)
* [headers]: an object (map) of headers which will be passed as
* request headers for the requested document. Accept is not
* allowed. (default: none).
* [httpAgent]: a Node.js `http.Agent` to use with 'http' requests.
* (default: none)
* [httpsAgent]: a Node.js `https.Agent` to use with 'https' requests.
* (default: An agent with rejectUnauthorized to the strictSSL
* value)
*
* @return the node document loader.
*/
module.exports = (
{ secure, strictSSL = true, maxRedirects = -1, headers = {}, httpAgent, httpsAgent } = {
strictSSL: true,
maxRedirects: -1,
headers: {},
}
) => {
headers = buildHeaders(headers)
// if no default user-agent header, copy headers and set one
if (!('user-agent' in headers)) {
headers = Object.assign({}, headers, {
'user-agent': 'jsonld.js',
})
}
const http = require('http')

const queue = new RequestQueue()
return queue.wrapLoader(function (url) {
return loadDocument(url, [])
})

async function loadDocument(url, redirects) {
const isHttp = url.startsWith('http:')
const isHttps = url.startsWith('https:')
if (!isHttp && !isHttps) {
throw new JsonLdError(
'URL could not be dereferenced; only "http" and "https" URLs are ' + 'supported.',
'jsonld.InvalidUrl',
{ code: 'loading document failed', url }
)
}
if (secure && !isHttps) {
throw new JsonLdError(
'URL could not be dereferenced; secure mode is enabled and ' + 'the URL\'s scheme is not "https".',
'jsonld.InvalidUrl',
{ code: 'loading document failed', url }
)
}
// TODO: disable cache until HTTP caching implemented
let doc = null //cache.get(url);
if (doc !== null) {
return doc
}

let alternate = null

const { res, body } = await _fetch({
url,
headers,
strictSSL,
httpAgent,
httpsAgent,
})
doc = { contextUrl: null, documentUrl: url, document: body || null }

// handle error
const statusText = http.STATUS_CODES[res.status]
if (res.status >= 400) {
throw new JsonLdError(`URL "${url}" could not be dereferenced: ${statusText}`, 'jsonld.InvalidUrl', {
code: 'loading document failed',
url,
httpStatusCode: res.status,
})
}
const link = res.headers.get('link')
let location = res.headers.get('location')
const contentType = res.headers.get('content-type')

// handle Link Header
if (link && contentType !== 'application/ld+json') {
// only 1 related link header permitted
const linkHeaders = parseLinkHeader(link)
const linkedContext = linkHeaders[LINK_HEADER_CONTEXT]
if (Array.isArray(linkedContext)) {
throw new JsonLdError(
'URL could not be dereferenced, it has more than one associated ' + 'HTTP Link Header.',
'jsonld.InvalidUrl',
{ code: 'multiple context link headers', url }
)
}
if (linkedContext) {
doc.contextUrl = linkedContext.target
}

// "alternate" link header is a redirect
alternate = linkHeaders['alternate']
if (
alternate &&
alternate.type == 'application/ld+json' &&
!(contentType || '').match(/^application\/(\w*\+)?json$/)
) {
location = prependBase(url, alternate.target)
}
}

// handle redirect
if ((alternate || (res.status >= 300 && res.status < 400)) && location) {
if (redirects.length === maxRedirects) {
throw new JsonLdError(
'URL could not be dereferenced; there were too many redirects.',
'jsonld.TooManyRedirects',
{
code: 'loading document failed',
url,
httpStatusCode: res.status,
redirects,
}
)
}
if (redirects.indexOf(url) !== -1) {
throw new JsonLdError(
'URL could not be dereferenced; infinite redirection was detected.',
'jsonld.InfiniteRedirectDetected',
{
code: 'recursive context inclusion',
url,
httpStatusCode: res.status,
redirects,
}
)
}
redirects.push(url)
return loadDocument(location, redirects)
}

// cache for each redirected URL
redirects.push(url)
// TODO: disable cache until HTTP caching implemented
/*
for(let i = 0; i < redirects.length; ++i) {
cache.set(
redirects[i],
{contextUrl: null, documentUrl: redirects[i], document: body});
}
*/

return doc
}
}

async function _fetch({ url, headers, strictSSL, httpAgent, httpsAgent }) {
try {
const options = { headers, redirect: 'manual' }
const isHttps = url.startsWith('https:')
if (isHttps) {
options.agent = httpsAgent || new https.Agent({ rejectUnauthorized: strictSSL })
} else {
if (httpAgent) {
options.agent = httpAgent
}
}
const res = await httpClient.get(url, options)
return { res, body: res.data }
} catch (e) {
// HTTP errors have a response in them
// ky considers redirects HTTP errors
if (e.response) {
return { res: e.response, body: null }
}
throw new JsonLdError('URL could not be dereferenced, an error occurred.', 'jsonld.LoadDocumentError', {
code: 'loading document failed',
url,
cause: e,
})
}
}
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -954,10 +954,10 @@
fast-text-encoding "^1.0.3"
isomorphic-webcrypto "^2.3.8"

"@digitalcredentials/vc@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@digitalcredentials/vc/-/vc-1.1.2.tgz#868a56962f5137c29eb51eea1ba60251ebf69ad1"
integrity sha512-TSgny9XUh+W7uFjdcpvZzN7I35F9YMTv6jVINXr7UaLNgrinIjy6A5RMGQH9ecpcaoLMemKB5XjtLOOOQ3vknQ==
"@digitalcredentials/vc@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@digitalcredentials/vc/-/vc-5.0.0.tgz#9940103221d9bdc280ce82ca615624ac2ef08d47"
integrity sha512-87ARRxlAdIuUPArbMYJ8vUY7QqkIvJGFrBwfTH1PcB8Wz1E/M4q3oc/WLrDyJNg4o/irVVB5gkA9iIntTYSpoA==
dependencies:
"@digitalcredentials/jsonld" "^5.2.1"
"@digitalcredentials/jsonld-signatures" "^9.3.1"
Expand Down