From 4a77591c57776d450773e64762f0951a84a9feb4 Mon Sep 17 00:00:00 2001 From: Yarmo Mackenbach Date: Tue, 23 Jan 2024 15:54:57 +0100 Subject: [PATCH] feat: support ASPE claims --- src/enums.js | 2 + src/fetcher/aspe.js | 77 +++++++++++++++++++++++++++++++ src/fetcher/index.js | 1 + src/fetcher/index.minimal.js | 1 + src/serviceProviders/aspe.js | 86 +++++++++++++++++++++++++++++++++++ src/serviceProviders/index.js | 2 + 6 files changed, 169 insertions(+) create mode 100644 src/fetcher/aspe.js create mode 100644 src/serviceProviders/aspe.js diff --git a/src/enums.js b/src/enums.js index c8c9ac2..dac0b30 100644 --- a/src/enums.js +++ b/src/enums.js @@ -40,6 +40,8 @@ export const ProxyPolicy = { export const Fetcher = { /** HTTP requests to ActivityPub */ ACTIVITYPUB: 'activitypub', + /** ASPE HTTP requests */ + ASPE: 'aspe', /** DNS module from Node.js */ DNS: 'dns', /** GraphQL over HTTP requests */ diff --git a/src/fetcher/aspe.js b/src/fetcher/aspe.js new file mode 100644 index 0000000..14c0bc1 --- /dev/null +++ b/src/fetcher/aspe.js @@ -0,0 +1,77 @@ +/* +Copyright 2024 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. +*/ +import axios from 'axios' +import isFQDN from 'validator/lib/isFQDN.js' +import { version } from '../constants.js' +import { parseProfileJws } from '../asp.js' + +export const timeout = 5000 + +/** + * Execute a fetch request + * @function + * @async + * @param {object} data - Data used in the request + * @param {string} data.aspeUri - ASPE URI of the targeted profile + * @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher + * @returns {Promise} + */ +export async function fn (data, opts) { + let timeoutHandle + const timeoutPromise = new Promise((resolve, reject) => { + timeoutHandle = setTimeout( + () => reject(new Error('Request was timed out')), + data.fetcherTimeout ? data.fetcherTimeout : timeout + ) + }) + + const fetchPromise = new Promise((resolve, reject) => { + const reURI = /^aspe:([a-zA-Z0-9.\-_]*):([a-zA-Z0-9]*)/ + const match = data.aspeUri.match(reURI) + + if (!data.aspeUri || !reURI.test(data.aspeUri) || !isFQDN(match[1])) { + reject(new Error('No valid ASPE URI provided')) + return + } + + const url = `https://${match[1]}/.well-known/aspe/id/${match[2].toUpperCase()}` + + axios.get(url, { + headers: { + Accept: 'application/asp+jwt', + 'User-Agent': `doipjs/${version}` + }, + validateStatus: (status) => status >= 200 && status < 400 + }) + .then(async res => await parseProfileJws(res.data, data.aspeUri)) + .then(profile => + profile.personas.flatMap(p => { return p.claims.map(c => c._uri) }) + ) + .then(res => { + resolve({ + claims: res + }) + }) + .catch(e => { + reject(e) + }) + }) + + return Promise.race([fetchPromise, timeoutPromise]).then((result) => { + clearTimeout(timeoutHandle) + return result + }) +} diff --git a/src/fetcher/index.js b/src/fetcher/index.js index 8e3d7cd..124c133 100644 --- a/src/fetcher/index.js +++ b/src/fetcher/index.js @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ export * as activitypub from './activitypub.js' +export * as aspe from './aspe.js' export * as dns from './dns.js' export * as graphql from './graphql.js' export * as http from './http.js' diff --git a/src/fetcher/index.minimal.js b/src/fetcher/index.minimal.js index cbd4b1e..b38f8b8 100644 --- a/src/fetcher/index.minimal.js +++ b/src/fetcher/index.minimal.js @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ export * as activitypub from './activitypub.js' +export * as aspe from './aspe.js' export * as graphql from './graphql.js' export * as http from './http.js' export * as matrix from './matrix.js' diff --git a/src/serviceProviders/aspe.js b/src/serviceProviders/aspe.js new file mode 100644 index 0000000..b0d2b60 --- /dev/null +++ b/src/serviceProviders/aspe.js @@ -0,0 +1,86 @@ +/* +Copyright 2024 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. +*/ +import isFQDN from 'validator/lib/isFQDN.js' +import * as E from '../enums.js' +import { ServiceProvider } from '../serviceProvider.js' + +export const reURI = /^aspe:([a-zA-Z0-9.\-_]*):([a-zA-Z0-9]*)/ + +/** + * @function + * @param {string} uri + */ +export function processURI (uri) { + const match = uri.match(reURI) + + if (!isFQDN(match[1])) { + return null + } + + return new ServiceProvider({ + about: { + id: 'aspe', + name: 'ASPE' + }, + profile: { + display: match[1], + uri, + qr: null + }, + claim: { + uriRegularExpression: reURI.toString(), + uriIsAmbiguous: false + }, + proof: { + request: { + uri: null, + fetcher: E.Fetcher.ASPE, + accessRestriction: E.ProofAccessRestriction.NONE, + data: { + aspeUri: uri + } + }, + response: { + format: E.ProofFormat.JSON + }, + target: [{ + format: E.ClaimFormat.URI, + encoding: E.EntityEncodingFormat.PLAIN, + relation: E.ClaimRelation.CONTAINS, + path: ['claims'] + }] + } + }) +} + +export const tests = [ + { + uri: 'aspe:domain.tld:abc123def456', + shouldMatch: true + }, + { + uri: 'aspe:domain.tld', + shouldMatch: false + }, + { + uri: 'dns:domain.tld', + shouldMatch: false + }, + { + uri: 'https://domain.tld', + shouldMatch: false + } +] diff --git a/src/serviceProviders/index.js b/src/serviceProviders/index.js index 6a446a5..eea1cc2 100644 --- a/src/serviceProviders/index.js +++ b/src/serviceProviders/index.js @@ -13,6 +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. */ +import * as aspe from './aspe.js' import * as openpgp from './openpgp.js' import * as dns from './dns.js' import * as irc from './irc.js' @@ -38,6 +39,7 @@ import * as keybase from './keybase.js' import * as opencollective from './opencollective.js' const _data = { + aspe, openpgp, dns, irc,