/* Copyright (C) 2023 Yarmo Mackenbach This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>. */ import express from 'express' import { check, validationResult } from 'express-validator' import Ajv from 'ajv/dist/2020.js' import * as dotenv from 'dotenv' import { Claim } from 'doipjs' import { generateAspeProfile, generateWKDProfile, generateHKPProfile, generateAutoProfile } from '../../server/index.js' import { claimSchema, personaSchema, profileSchema, serviceProviderSchema } from '../../schemas.js' dotenv.config() const router = express.Router() const ajv = new Ajv({ schemas: [profileSchema, personaSchema, claimSchema, serviceProviderSchema] }) const apiProfileValidate = ajv.compile(profileSchema) const doVerification = async (profile) => { const promises = [] const results = [] const verificationOptions = { proxy: { hostname: process.env.PROXY_HOSTNAME, policy: (process.env.PROXY_HOSTNAME !== '') ? 'adaptive' : 'never' } } // Return early if no users in key if (!profile.personas) { return profile } for (let iUser = 0; iUser < profile.personas.length; iUser++) { const user = profile.personas[iUser] for (let iClaim = 0; iClaim < user.claims.length; iClaim++) { const claim = user.claims[iClaim] promises.push( new Promise((resolve, reject) => { (async () => { await claim.verify(verificationOptions) results.push([iUser, iClaim, claim]) resolve() })() }) ) } } await Promise.all(promises) results.forEach(result => { profile.personas[result[0]].claims[result[1]] = result[2] }) return profile } const validate = (profile) => { const valid = apiProfileValidate(profile) if (!valid) { throw new Error(`Profile data validation error: ${apiProfileValidate.errors.map(x => x.message).join(', ')}`) } } router.get('/fetch', check('query').exists(), check('protocol').optional().toLowerCase().isIn(['aspe', 'hkp', 'wkd']), check('doVerification').default(false).isBoolean().toBoolean(), async (req, res) => { const valRes = validationResult(req) if (!valRes.isEmpty()) { res.status(400).send(valRes) return } // Generate profile let data switch (req.query.protocol) { case 'aspe': data = await generateAspeProfile(req.query.query) break case 'wkd': data = await generateWKDProfile(req.query.query) break case 'hkp': data = await generateHKPProfile(req.query.query) break default: data = await generateAutoProfile(req.query.query) break } if ('errors' in data && data.errors.length > 0) { res.status(500).send(data) } // Do verification if (req.query.doVerification) { data = await doVerification(data) } try { data = data.toJSON() } catch (error) { data = { errors: [error.message] } } try { // Validate JSON validate(data) } catch (error) { data = { errors: [error.message] } } let statusCode = 200 if ('errors' in data && data.errors.length > 0) { statusCode = 500 } res.status(statusCode).send(data) } ) router.get('/verify', check('data').exists().isJSON(), async (req, res) => { const valRes = validationResult(req) if (!valRes.isEmpty()) { res.status(400).send(valRes) return } const profile = Claim.fromJson(req.query.data) // Do verification let data = await doVerification(profile) try { data = data.toJSON() } catch (error) { data = { errors: [error.message] } } try { // Validate JSON validate(data) } catch (error) { data = { errors: [error.message] } } let statusCode = 200 if ('errors' in data) { statusCode = 500 } res.status(statusCode).send(data) } ) export default router