feat: support ASPE claims

This commit is contained in:
Yarmo Mackenbach 2024-01-23 15:54:57 +01:00
parent cd96131ad1
commit 4a77591c57
No known key found for this signature in database
GPG key ID: C248C28D432560ED
6 changed files with 169 additions and 0 deletions

View file

@ -40,6 +40,8 @@ export const ProxyPolicy = {
export const Fetcher = { export const Fetcher = {
/** HTTP requests to ActivityPub */ /** HTTP requests to ActivityPub */
ACTIVITYPUB: 'activitypub', ACTIVITYPUB: 'activitypub',
/** ASPE HTTP requests */
ASPE: 'aspe',
/** DNS module from Node.js */ /** DNS module from Node.js */
DNS: 'dns', DNS: 'dns',
/** GraphQL over HTTP requests */ /** GraphQL over HTTP requests */

77
src/fetcher/aspe.js Normal file
View file

@ -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<object|string>}
*/
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
})
}

View file

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
export * as activitypub from './activitypub.js' export * as activitypub from './activitypub.js'
export * as aspe from './aspe.js'
export * as dns from './dns.js' export * as dns from './dns.js'
export * as graphql from './graphql.js' export * as graphql from './graphql.js'
export * as http from './http.js' export * as http from './http.js'

View file

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
export * as activitypub from './activitypub.js' export * as activitypub from './activitypub.js'
export * as aspe from './aspe.js'
export * as graphql from './graphql.js' export * as graphql from './graphql.js'
export * as http from './http.js' export * as http from './http.js'
export * as matrix from './matrix.js' export * as matrix from './matrix.js'

View file

@ -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
}
]

View file

@ -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 See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import * as aspe from './aspe.js'
import * as openpgp from './openpgp.js' import * as openpgp from './openpgp.js'
import * as dns from './dns.js' import * as dns from './dns.js'
import * as irc from './irc.js' import * as irc from './irc.js'
@ -38,6 +39,7 @@ import * as keybase from './keybase.js'
import * as opencollective from './opencollective.js' import * as opencollective from './opencollective.js'
const _data = { const _data = {
aspe,
openpgp, openpgp,
dns, dns,
irc, irc,