forked from Mirrors/keyoxide-web
feat: update server code
This commit is contained in:
parent
6676c78961
commit
59fc51c407
8 changed files with 559 additions and 268 deletions
|
@ -28,16 +28,19 @@ 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/>.
|
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import apiRouter0 from '../api/v0/index.js'
|
import apiRouter3 from '../api/v3/index.js'
|
||||||
import apiRouter1 from '../api/v1/index.js'
|
|
||||||
import apiRouter2 from '../api/v2/index.js'
|
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
|
||||||
if ((process.env.ENABLE_MAIN_MODULE ?? 'true') === 'true') {
|
router.get('/0', (req, res) => {
|
||||||
router.use('/0', apiRouter0)
|
return res.status(501).send('Proxy v0 API endpoint is no longer supported, please migrate to proxy v3 API endpoint')
|
||||||
}
|
})
|
||||||
router.use('/1', apiRouter1)
|
router.get('/1', (req, res) => {
|
||||||
router.use('/2', apiRouter2)
|
return res.status(501).send('Proxy v1 API endpoint is no longer supported, please migrate to proxy v3 API endpoint')
|
||||||
|
})
|
||||||
|
router.get('/2', (req, res) => {
|
||||||
|
return res.status(501).send('Proxy v2 API endpoint is no longer supported, please migrate to proxy v3 API endpoint')
|
||||||
|
})
|
||||||
|
router.use('/3', apiRouter3)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
|
@ -30,7 +30,6 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import markdownImport from 'markdown-it'
|
import markdownImport from 'markdown-it'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import demoData from '../server/demo.js'
|
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const md = markdownImport({ typographer: true })
|
const md = markdownImport({ typographer: true })
|
||||||
|
@ -48,7 +47,7 @@ router.get('/', (req, res) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.render('index', { highlights, demoData })
|
res.render('index', { highlights })
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/privacy', (req, res) => {
|
router.get('/privacy', (req, res) => {
|
||||||
|
|
|
@ -30,6 +30,7 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import bodyParserImport from 'body-parser'
|
import bodyParserImport from 'body-parser'
|
||||||
import { generateSignatureProfile, utils, generateWKDProfile, generateHKPProfile, generateAutoProfile, generateKeybaseProfile } from '../server/index.js'
|
import { generateSignatureProfile, utils, generateWKDProfile, generateHKPProfile, generateAutoProfile, generateKeybaseProfile } from '../server/index.js'
|
||||||
|
import { Profile } from 'doipjs'
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const bodyParser = bodyParserImport.urlencoded({ extended: false })
|
const bodyParser = bodyParserImport.urlencoded({ extended: false })
|
||||||
|
@ -41,10 +42,10 @@ router.get('/sig', (req, res) => {
|
||||||
router.post('/sig', bodyParser, async (req, res) => {
|
router.post('/sig', bodyParser, async (req, res) => {
|
||||||
const data = await generateSignatureProfile(req.body.signature)
|
const data = await generateSignatureProfile(req.body.signature)
|
||||||
const title = utils.generatePageTitle('profile', data)
|
const title = utils.generatePageTitle('profile', data)
|
||||||
res.set('ariadne-identity-proof', data.keyData.openpgp4fpr)
|
res.set('ariadne-identity-proof', data.identifier)
|
||||||
res.render('profile', {
|
res.render('profile', {
|
||||||
title,
|
title,
|
||||||
data,
|
data: data instanceof Profile ? data.toJSON() : data,
|
||||||
isSignature: true,
|
isSignature: true,
|
||||||
signature: req.body.signature,
|
signature: req.body.signature,
|
||||||
enable_message_encryption: false,
|
enable_message_encryption: false,
|
||||||
|
@ -55,10 +56,10 @@ router.post('/sig', bodyParser, async (req, res) => {
|
||||||
router.get('/wkd/:id', async (req, res) => {
|
router.get('/wkd/:id', async (req, res) => {
|
||||||
const data = await generateWKDProfile(req.params.id)
|
const data = await generateWKDProfile(req.params.id)
|
||||||
const title = utils.generatePageTitle('profile', data)
|
const title = utils.generatePageTitle('profile', data)
|
||||||
res.set('ariadne-identity-proof', data.keyData.openpgp4fpr)
|
res.set('ariadne-identity-proof', data.identifier)
|
||||||
res.render('profile', {
|
res.render('profile', {
|
||||||
title,
|
title,
|
||||||
data,
|
data: data instanceof Profile ? data.toJSON() : data,
|
||||||
enable_message_encryption: false,
|
enable_message_encryption: false,
|
||||||
enable_signature_verification: false
|
enable_signature_verification: false
|
||||||
})
|
})
|
||||||
|
@ -67,10 +68,10 @@ router.get('/wkd/:id', async (req, res) => {
|
||||||
router.get('/hkp/:id', async (req, res) => {
|
router.get('/hkp/:id', async (req, res) => {
|
||||||
const data = await generateHKPProfile(req.params.id)
|
const data = await generateHKPProfile(req.params.id)
|
||||||
const title = utils.generatePageTitle('profile', data)
|
const title = utils.generatePageTitle('profile', data)
|
||||||
res.set('ariadne-identity-proof', data.keyData.openpgp4fpr)
|
res.set('ariadne-identity-proof', data.identifier)
|
||||||
res.render('profile', {
|
res.render('profile', {
|
||||||
title,
|
title,
|
||||||
data,
|
data: data instanceof Profile ? data.toJSON() : data,
|
||||||
enable_message_encryption: false,
|
enable_message_encryption: false,
|
||||||
enable_signature_verification: false
|
enable_signature_verification: false
|
||||||
})
|
})
|
||||||
|
@ -79,10 +80,10 @@ router.get('/hkp/:id', async (req, res) => {
|
||||||
router.get('/hkp/:server/:id', async (req, res) => {
|
router.get('/hkp/:server/:id', async (req, res) => {
|
||||||
const data = await generateHKPProfile(req.params.id, req.params.server)
|
const data = await generateHKPProfile(req.params.id, req.params.server)
|
||||||
const title = utils.generatePageTitle('profile', data)
|
const title = utils.generatePageTitle('profile', data)
|
||||||
res.set('ariadne-identity-proof', data.keyData.openpgp4fpr)
|
res.set('ariadne-identity-proof', data.identifier)
|
||||||
res.render('profile', {
|
res.render('profile', {
|
||||||
title,
|
title,
|
||||||
data,
|
data: data instanceof Profile ? data.toJSON() : data,
|
||||||
enable_message_encryption: false,
|
enable_message_encryption: false,
|
||||||
enable_signature_verification: false
|
enable_signature_verification: false
|
||||||
})
|
})
|
||||||
|
@ -91,10 +92,10 @@ router.get('/hkp/:server/:id', async (req, res) => {
|
||||||
router.get('/keybase/:username/:fingerprint', async (req, res) => {
|
router.get('/keybase/:username/:fingerprint', async (req, res) => {
|
||||||
const data = await generateKeybaseProfile(req.params.username, req.params.fingerprint)
|
const data = await generateKeybaseProfile(req.params.username, req.params.fingerprint)
|
||||||
const title = utils.generatePageTitle('profile', data)
|
const title = utils.generatePageTitle('profile', data)
|
||||||
res.set('ariadne-identity-proof', data.keyData.openpgp4fpr)
|
res.set('ariadne-identity-proof', data.identifier)
|
||||||
res.render('profile', {
|
res.render('profile', {
|
||||||
title,
|
title,
|
||||||
data,
|
data: data instanceof Profile ? data.toJSON() : data,
|
||||||
enable_message_encryption: false,
|
enable_message_encryption: false,
|
||||||
enable_signature_verification: false
|
enable_signature_verification: false
|
||||||
})
|
})
|
||||||
|
@ -103,10 +104,10 @@ router.get('/keybase/:username/:fingerprint', async (req, res) => {
|
||||||
router.get('/:id', async (req, res) => {
|
router.get('/:id', async (req, res) => {
|
||||||
const data = await generateAutoProfile(req.params.id)
|
const data = await generateAutoProfile(req.params.id)
|
||||||
const title = utils.generatePageTitle('profile', data)
|
const title = utils.generatePageTitle('profile', data)
|
||||||
res.set('ariadne-identity-proof', data.keyData.openpgp4fpr)
|
res.set('ariadne-identity-proof', data.identifier)
|
||||||
res.render('profile', {
|
res.render('profile', {
|
||||||
title,
|
title,
|
||||||
data,
|
data: data instanceof Profile ? data.toJSON() : data,
|
||||||
enable_message_encryption: false,
|
enable_message_encryption: false,
|
||||||
enable_signature_verification: false
|
enable_signature_verification: false
|
||||||
})
|
})
|
||||||
|
|
376
src/schemas.js
Normal file
376
src/schemas.js
Normal file
|
@ -0,0 +1,376 @@
|
||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
export const profileSchema = {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://spec.keyoxide.org/2/profile.schema.json",
|
||||||
|
"title": "Profile",
|
||||||
|
"description": "Keyoxide profile with personas",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"profileVersion": {
|
||||||
|
"description": "The version of the profile",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"profileType": {
|
||||||
|
"description": "The type of the profile [openpgp, asp]",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"identifier": {
|
||||||
|
"description": "Identifier of the profile (email, fingerprint, URI)",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"personas": {
|
||||||
|
"description": "The personas inside the profile",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "https://spec.keyoxide.org/2/persona.schema.json"
|
||||||
|
},
|
||||||
|
"minItems": 1,
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
"primaryPersonaIndex": {
|
||||||
|
"description": "The index of the primary persona",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"publicKey": {
|
||||||
|
"description": "The cryptographic key associated with the profile",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"keyType": {
|
||||||
|
"description": "The type of cryptographic key [eddsa, es256, openpgp, none]",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"encoding": {
|
||||||
|
"description": "The encoding of the cryptographic key [pem, jwk, armored_pgp, none]",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"encodedKey": {
|
||||||
|
"description": "The encoded cryptographic key (PEM, stringified JWK, ...)",
|
||||||
|
"type": ["string", "null"]
|
||||||
|
},
|
||||||
|
"fetch": {
|
||||||
|
"description": "Details on how to fetch the public key",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"method": {
|
||||||
|
"description": "The method to fetch the key [aspe, hkp, wkd, http, none]",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"query": {
|
||||||
|
"description": "The query to fetch the key",
|
||||||
|
"type": ["string", "null"]
|
||||||
|
},
|
||||||
|
"resolvedUrl": {
|
||||||
|
"description": "The URL the method eventually resolved to",
|
||||||
|
"type": ["string", "null"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"keyType",
|
||||||
|
"fetch"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"verifiers": {
|
||||||
|
"description": "A list of links to verifiers",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "Name of the verifier site",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"description": "URL to the profile page on the verifier site",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"profileVersion",
|
||||||
|
"profileType",
|
||||||
|
"identifier",
|
||||||
|
"personas",
|
||||||
|
"primaryPersonaIndex",
|
||||||
|
"publicKey",
|
||||||
|
"verifiers"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const personaSchema = {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://spec.keyoxide.org/2/persona.schema.json",
|
||||||
|
"title": "Profile",
|
||||||
|
"description": "Keyoxide persona with identity claims",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"identifier": {
|
||||||
|
"description": "Identifier of the persona",
|
||||||
|
"type": ["string", "null"]
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "Name of the persona",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"description": "Email address of the persona",
|
||||||
|
"type": ["string", "null"]
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"description": "Description of the persona",
|
||||||
|
"type": ["string", "null"]
|
||||||
|
},
|
||||||
|
"avatarUrl": {
|
||||||
|
"description": "URL to an avatar image",
|
||||||
|
"type": ["string", "null"]
|
||||||
|
},
|
||||||
|
"isRevoked": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"claims": {
|
||||||
|
"description": "A list of identity claims",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "https://spec.keyoxide.org/2/claim.schema.json"
|
||||||
|
},
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"claims"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
|
||||||
|
export const claimSchema = {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://spec.keyoxide.org/2/claim.schema.json",
|
||||||
|
"title": "Identity claim",
|
||||||
|
"description": "Verifiable online identity claim",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"claimVersion": {
|
||||||
|
"description": "The version of the claim",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"uri": {
|
||||||
|
"description": "The claim URI",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"proofs": {
|
||||||
|
"description": "The proofs that would verify the claim",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"minItems": 1,
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
"matches": {
|
||||||
|
"description": "Service providers matched to the claim",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "https://spec.keyoxide.org/2/serviceprovider.schema.json"
|
||||||
|
},
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Claim status code"
|
||||||
|
},
|
||||||
|
"display": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Account name to display in the user interface"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "URL to link to in the user interface"
|
||||||
|
},
|
||||||
|
"serviceProviderName": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
"description": "Name of the service provider to display in the user interface"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"claimVersion",
|
||||||
|
"uri",
|
||||||
|
"proofs",
|
||||||
|
"status",
|
||||||
|
"display"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
|
||||||
|
export const serviceProviderSchema = {
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://spec.keyoxide.org/2/serviceprovider.schema.json",
|
||||||
|
"title": "Service provider",
|
||||||
|
"description": "A service provider that can be matched to identity claims",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"about": {
|
||||||
|
"description": "Details about the service provider",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "Full name of the service provider",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "Identifier of the service provider (no whitespace or symbols, lowercase)",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"homepage": {
|
||||||
|
"description": "URL to the homepage of the service provider",
|
||||||
|
"type": ["string", "null"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"description": "What the profile would look like if the match is correct",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"display": {
|
||||||
|
"description": "Profile name to be displayed",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uri": {
|
||||||
|
"description": "URI or URL for public access to the profile",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"qr": {
|
||||||
|
"description": "URI or URL associated with the profile usually served as a QR code",
|
||||||
|
"type": ["string", "null"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"claim": {
|
||||||
|
"description": "Details from the claim matching process",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"uriRegularExpression": {
|
||||||
|
"description": "Regular expression used to parse the URI",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uriIsAmbiguous": {
|
||||||
|
"description": "Whether this match automatically excludes other matches",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"proof": {
|
||||||
|
"description": "Information for the proof verification process",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"request": {
|
||||||
|
"description": "Details to request the potential proof",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"uri": {
|
||||||
|
"description": "Location of the proof",
|
||||||
|
"type": ["string", "null"]
|
||||||
|
},
|
||||||
|
"accessRestriction": {
|
||||||
|
"description": "Type of access restriction [none, nocors, granted, server]",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"fetcher": {
|
||||||
|
"description": "Name of the fetcher to use",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"description": "Data needed by the fetcher or proxy to request the proof",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"description": "Details about the expected response",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"format": {
|
||||||
|
"description": "Expected format of the proof [text, json]",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"description": "Details about the target located in the response",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"format": {
|
||||||
|
"description": "How is the proof formatted [uri, fingerprint]",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"encoding": {
|
||||||
|
"description": "How is the proof encoded [plain, html, xml]",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"relation": {
|
||||||
|
"description": "How are the response and the target related [contains, equals]",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"description": "Path to the target location if the response is JSON",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"about",
|
||||||
|
"profile",
|
||||||
|
"claim",
|
||||||
|
"proof"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
|
@ -1,81 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2021 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/>.
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
claimVersion: 1,
|
|
||||||
uri: 'https://fosstodon.org/@keyoxide',
|
|
||||||
fingerprint: '9f0048ac0b23301e1f77e994909f6bd6f80f485d',
|
|
||||||
status: 'verified',
|
|
||||||
matches: [
|
|
||||||
{
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'web',
|
|
||||||
name: 'mastodon (demo)'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: {},
|
|
||||||
isAmbiguous: true
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: '@keyoxide@fosstodon.org',
|
|
||||||
uri: 'https://fosstodon.org/@keyoxide',
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: 'https://fosstodon.org/@keyoxide',
|
|
||||||
request: {
|
|
||||||
fetcher: 'http',
|
|
||||||
access: 0,
|
|
||||||
format: 'json',
|
|
||||||
data: {
|
|
||||||
url: 'https://fosstodon.org/@keyoxide',
|
|
||||||
format: 'json'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: 1,
|
|
||||||
relation: 0,
|
|
||||||
path: [
|
|
||||||
'attachment',
|
|
||||||
'value'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
verification: {
|
|
||||||
result: true,
|
|
||||||
completed: true,
|
|
||||||
errors: [],
|
|
||||||
proof: {
|
|
||||||
fetcher: 'http',
|
|
||||||
viaProxy: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -29,45 +29,48 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
|
||||||
*/
|
*/
|
||||||
import logger from '../log.js'
|
import logger from '../log.js'
|
||||||
import * as doipjs from 'doipjs'
|
import * as doipjs from 'doipjs'
|
||||||
import { fetchWKD, fetchHKP, fetchSignature, fetchKeybase } from './keys.js'
|
import { fetchWKD, fetchHKP, fetchSignature, fetchKeybase } from './openpgpProfiles.js'
|
||||||
import libravatar from 'libravatar'
|
import libravatar from 'libravatar'
|
||||||
|
|
||||||
|
const generateAspeProfile = async (id) => {
|
||||||
|
logger.debug('Generating an ASPE profile',
|
||||||
|
{ component: 'aspe_profile_generator', action: 'start', profile_id: id })
|
||||||
|
|
||||||
|
return doipjs.asp.fetchASPE(id)
|
||||||
|
.then(profile => {
|
||||||
|
profile.addVerifier('keyoxide', `https://${process.env.DOMAIN}/${id}`)
|
||||||
|
profile = processAspProfile(profile)
|
||||||
|
return profile
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
logger.warn('Failed to generate ASPE profile',
|
||||||
|
{ component: 'aspe_profile_generator', action: 'failure', error: err.message, profile_id: id })
|
||||||
|
|
||||||
|
return {
|
||||||
|
errors: [err.message]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const generateWKDProfile = async (id) => {
|
const generateWKDProfile = async (id) => {
|
||||||
logger.debug('Generating a WKD profile',
|
logger.debug('Generating a WKD profile',
|
||||||
{ component: 'wkd_profile_generator', action: 'start', profile_id: id })
|
{ component: 'wkd_profile_generator', action: 'start', profile_id: id })
|
||||||
|
|
||||||
return fetchWKD(id)
|
return fetchWKD(id)
|
||||||
.then(async key => {
|
.then(async profile => {
|
||||||
let keyData = await doipjs.keys.process(key.publicKey)
|
profile.addVerifier('keyoxide', `https://${process.env.DOMAIN}/wkd/${id}`)
|
||||||
keyData.openpgp4fpr = `openpgp4fpr:${keyData.fingerprint.toLowerCase()}`
|
profile = processOpenPgpProfile(profile)
|
||||||
keyData.key.fetchMethod = 'wkd'
|
|
||||||
keyData.key.uri = key.fetchURL
|
|
||||||
keyData.key.data = {}
|
|
||||||
keyData = processKeyData(keyData)
|
|
||||||
|
|
||||||
const keyoxideData = {}
|
|
||||||
keyoxideData.url = `https://${process.env.DOMAIN}/wkd/${id}`
|
|
||||||
|
|
||||||
logger.debug('Generating a WKD profile',
|
logger.debug('Generating a WKD profile',
|
||||||
{ component: 'wkd_profile_generator', action: 'done', profile_id: id })
|
{ component: 'wkd_profile_generator', action: 'done', profile_id: id })
|
||||||
|
|
||||||
return {
|
return profile
|
||||||
key,
|
|
||||||
keyData,
|
|
||||||
keyoxide: keyoxideData,
|
|
||||||
extra: await computeExtraData(key, keyData),
|
|
||||||
errors: []
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
logger.warn('Failed to generate WKD profile',
|
logger.warn('Failed to generate WKD profile',
|
||||||
{ component: 'wkd_profile_generator', action: 'failure', error: err.message, profile_id: id })
|
{ component: 'wkd_profile_generator', action: 'failure', error: err.message, profile_id: id })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: {},
|
|
||||||
keyData: {},
|
|
||||||
keyoxide: {},
|
|
||||||
extra: {},
|
|
||||||
errors: [err.message]
|
errors: [err.message]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -78,41 +81,27 @@ const generateHKPProfile = async (id, keyserverDomain) => {
|
||||||
{ component: 'hkp_profile_generator', action: 'start', profile_id: id, keyserver_domain: keyserverDomain || '' })
|
{ component: 'hkp_profile_generator', action: 'start', profile_id: id, keyserver_domain: keyserverDomain || '' })
|
||||||
|
|
||||||
return fetchHKP(id, keyserverDomain)
|
return fetchHKP(id, keyserverDomain)
|
||||||
.then(async key => {
|
.then(async profile => {
|
||||||
let keyData = await doipjs.keys.process(key.publicKey)
|
let keyoxideUrl
|
||||||
keyData.openpgp4fpr = `openpgp4fpr:${keyData.fingerprint.toLowerCase()}`
|
|
||||||
keyData.key.fetchMethod = 'hkp'
|
|
||||||
keyData.key.uri = key.fetchURL
|
|
||||||
keyData.key.data = {}
|
|
||||||
keyData = processKeyData(keyData)
|
|
||||||
|
|
||||||
const keyoxideData = {}
|
|
||||||
if (!keyserverDomain || keyserverDomain === 'keys.openpgp.org') {
|
if (!keyserverDomain || keyserverDomain === 'keys.openpgp.org') {
|
||||||
keyoxideData.url = `https://${process.env.DOMAIN}/hkp/${id}`
|
keyoxideUrl = `https://${process.env.DOMAIN}/hkp/${id}`
|
||||||
} else {
|
} else {
|
||||||
keyoxideData.url = `https://${process.env.DOMAIN}/hkp/${keyserverDomain}/${id}`
|
keyoxideUrl = `https://${process.env.DOMAIN}/hkp/${keyserverDomain}/${id}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profile.addVerifier('keyoxide', keyoxideUrl)
|
||||||
|
profile = processOpenPgpProfile(profile)
|
||||||
|
|
||||||
logger.debug('Generating a HKP profile',
|
logger.debug('Generating a HKP profile',
|
||||||
{ component: 'hkp_profile_generator', action: 'done', profile_id: id, keyserver_domain: keyserverDomain || '' })
|
{ component: 'hkp_profile_generator', action: 'done', profile_id: id, keyserver_domain: keyserverDomain || '' })
|
||||||
|
|
||||||
return {
|
return profile
|
||||||
key,
|
|
||||||
keyData,
|
|
||||||
keyoxide: keyoxideData,
|
|
||||||
extra: await computeExtraData(key, keyData),
|
|
||||||
errors: []
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
logger.warn('Failed to generate HKP profile',
|
logger.warn('Failed to generate HKP profile',
|
||||||
{ component: 'hkp_profile_generator', action: 'failure', error: err.message, profile_id: id, keyserver_domain: keyserverDomain || '' })
|
{ component: 'hkp_profile_generator', action: 'failure', error: err.message, profile_id: id, keyserver_domain: keyserverDomain || '' })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: {},
|
|
||||||
keyData: {},
|
|
||||||
keyoxide: {},
|
|
||||||
extra: {},
|
|
||||||
errors: [err.message]
|
errors: [err.message]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -121,25 +110,31 @@ const generateHKPProfile = async (id, keyserverDomain) => {
|
||||||
const generateAutoProfile = async (id) => {
|
const generateAutoProfile = async (id) => {
|
||||||
let result
|
let result
|
||||||
|
|
||||||
|
const aspeRe = /aspe:(.*):(.*)/
|
||||||
|
|
||||||
|
if (aspeRe.test(id)) {
|
||||||
|
result = await generateAspeProfile(id)
|
||||||
|
|
||||||
|
if (result && !('errors' in result)) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (id.includes('@')) {
|
if (id.includes('@')) {
|
||||||
result = await generateWKDProfile(id)
|
result = await generateWKDProfile(id)
|
||||||
|
|
||||||
if (result && result.errors.length === 0) {
|
if (result && !('errors' in result)) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result = await generateHKPProfile(id)
|
result = await generateHKPProfile(id)
|
||||||
if (result && result.errors.length === 0) {
|
if (result && !('errors' in result)) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: {},
|
errors: ['No public profile/keys could be found']
|
||||||
keyData: {},
|
|
||||||
keyoxide: {},
|
|
||||||
extra: {},
|
|
||||||
errors: ['No public keys could be found']
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,34 +144,20 @@ const generateSignatureProfile = async (signature) => {
|
||||||
|
|
||||||
return fetchSignature(signature)
|
return fetchSignature(signature)
|
||||||
.then(async key => {
|
.then(async key => {
|
||||||
let keyData = key.keyData
|
let profile = await doipjs.signatures.parse(key.publicKey)
|
||||||
keyData.openpgp4fpr = `openpgp4fpr:${keyData.fingerprint.toLowerCase()}`
|
profile.addVerifier('keyoxide', keyoxideUrl)
|
||||||
key.keyData = undefined
|
profile = processOpenPgpProfile(profile)
|
||||||
keyData.key.data = {}
|
|
||||||
keyData = processKeyData(keyData)
|
|
||||||
|
|
||||||
const keyoxideData = {}
|
|
||||||
|
|
||||||
logger.debug('Generating a signature profile',
|
logger.debug('Generating a signature profile',
|
||||||
{ component: 'signature_profile_generator', action: 'done' })
|
{ component: 'signature_profile_generator', action: 'done' })
|
||||||
|
|
||||||
return {
|
return profile
|
||||||
key,
|
|
||||||
keyData,
|
|
||||||
keyoxide: keyoxideData,
|
|
||||||
extra: await computeExtraData(key, keyData),
|
|
||||||
errors: []
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
logger.warn('Failed to generate a signature profile',
|
logger.warn('Failed to generate a signature profile',
|
||||||
{ component: 'signature_profile_generator', action: 'failure', error: err.message })
|
{ component: 'signature_profile_generator', action: 'failure', error: err.message })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: {},
|
|
||||||
keyData: {},
|
|
||||||
keyoxide: {},
|
|
||||||
extra: {},
|
|
||||||
errors: [err.message]
|
errors: [err.message]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -187,72 +168,91 @@ const generateKeybaseProfile = async (username, fingerprint) => {
|
||||||
{ component: 'keybase_profile_generator', action: 'start', username, fingerprint })
|
{ component: 'keybase_profile_generator', action: 'start', username, fingerprint })
|
||||||
|
|
||||||
return fetchKeybase(username, fingerprint)
|
return fetchKeybase(username, fingerprint)
|
||||||
.then(async key => {
|
.then(async profile => {
|
||||||
let keyData = await doipjs.keys.process(key.publicKey)
|
profile.addVerifier('keyoxide', `https://${process.env.DOMAIN}/keybase/${username}/${fingerprint}`)
|
||||||
keyData.openpgp4fpr = `openpgp4fpr:${keyData.fingerprint.toLowerCase()}`
|
profile = processOpenPgpProfile(profile)
|
||||||
keyData.key.fetchMethod = 'hkp'
|
|
||||||
keyData.key.uri = key.fetchURL
|
|
||||||
keyData.key.data = {}
|
|
||||||
keyData = processKeyData(keyData)
|
|
||||||
|
|
||||||
const keyoxideData = {}
|
|
||||||
keyoxideData.url = `https://${process.env.DOMAIN}/keybase/${username}/${fingerprint}`
|
|
||||||
|
|
||||||
logger.debug('Generating a Keybase profile',
|
logger.debug('Generating a Keybase profile',
|
||||||
{ component: 'keybase_profile_generator', action: 'done', username, fingerprint })
|
{ component: 'keybase_profile_generator', action: 'done', username, fingerprint })
|
||||||
|
|
||||||
return {
|
return profile
|
||||||
key,
|
|
||||||
keyData,
|
|
||||||
keyoxide: keyoxideData,
|
|
||||||
extra: await computeExtraData(key, keyData),
|
|
||||||
errors: []
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
logger.warn('Failed to generate a Keybase profile',
|
logger.warn('Failed to generate a Keybase profile',
|
||||||
{ component: 'keybase_profile_generator', action: 'failure', error: err.message, username, fingerprint })
|
{ component: 'keybase_profile_generator', action: 'failure', error: err.message, username, fingerprint })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: {},
|
|
||||||
keyData: {},
|
|
||||||
keyoxide: {},
|
|
||||||
extra: {},
|
|
||||||
errors: [err.message]
|
errors: [err.message]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const processKeyData = (keyData) => {
|
const processAspProfile = async (/** @type {import('doipjs').Profile */ profile) => {
|
||||||
keyData.users.forEach(user => {
|
profile.personas.forEach(persona => {
|
||||||
// Remove faulty claims
|
// Remove faulty claims
|
||||||
user.claims = user.claims.filter(claim => {
|
persona.claims = persona.claims.filter(claim => {
|
||||||
return claim instanceof doipjs.Claim
|
return claim instanceof doipjs.Claim
|
||||||
})
|
})
|
||||||
|
|
||||||
// Match claims
|
// Match claims
|
||||||
user.claims.forEach(claim => {
|
persona.claims.forEach(claim => {
|
||||||
claim.match()
|
claim.match()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Sort claims
|
// Sort claims
|
||||||
user.claims.sort((a, b) => {
|
persona.claims.sort((a, b) => {
|
||||||
if (a.matches.length === 0) return 1
|
if (a.matches.length === 0) return 1
|
||||||
if (b.matches.length === 0) return -1
|
if (b.matches.length === 0) return -1
|
||||||
|
|
||||||
if (a.matches[0].serviceprovider.name < b.matches[0].serviceprovider.name) {
|
if (a.matches[0].about.name < b.matches[0].about.name) {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
if (a.matches[0].serviceprovider.name > b.matches[0].serviceprovider.name) {
|
if (a.matches[0].about.name > b.matches[0].about.name) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
keyData.primaryUserIndex ||= 0
|
// Overwrite avatarUrl
|
||||||
|
// TODO: don't overwrite avatarUrl once it's fully supported
|
||||||
|
profile.personas[profile.primaryPersonaIndex].avatarUrl = `https://api.dicebear.com/6.x/shapes/svg?seed=${profile.publicKey.fingerprint}&size=128`
|
||||||
|
|
||||||
return keyData
|
return profile
|
||||||
|
}
|
||||||
|
|
||||||
|
const processOpenPgpProfile = async (/** @type {import('doipjs').Profile */ profile) => {
|
||||||
|
profile.personas.forEach(persona => {
|
||||||
|
// Remove faulty claims
|
||||||
|
persona.claims = persona.claims.filter(claim => {
|
||||||
|
return claim instanceof doipjs.Claim
|
||||||
|
})
|
||||||
|
|
||||||
|
// Match claims
|
||||||
|
persona.claims.forEach(claim => {
|
||||||
|
claim.match()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Sort claims
|
||||||
|
persona.claims.sort((a, b) => {
|
||||||
|
if (a.matches.length === 0) return 1
|
||||||
|
if (b.matches.length === 0) return -1
|
||||||
|
|
||||||
|
if (a.matches[0].about.name < b.matches[0].about.name) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if (a.matches[0].about.name > b.matches[0].about.name) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Overwrite avatarUrl
|
||||||
|
// TODO: don't overwrite avatarUrl once it's fully supported
|
||||||
|
profile.personas[profile.primaryPersonaIndex].avatarUrl = await libravatar.get_avatar_url({ email: profile.personas[profile.primaryPersonaIndex].email, size: 128, default: 'mm', https: true })
|
||||||
|
|
||||||
|
return profile
|
||||||
}
|
}
|
||||||
|
|
||||||
const computeExtraData = async (key, keyData) => {
|
const computeExtraData = async (key, keyData) => {
|
||||||
|
@ -265,6 +265,7 @@ const computeExtraData = async (key, keyData) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { generateAspeProfile }
|
||||||
export { generateWKDProfile }
|
export { generateWKDProfile }
|
||||||
export { generateHKPProfile }
|
export { generateHKPProfile }
|
||||||
export { generateAutoProfile }
|
export { generateAutoProfile }
|
||||||
|
|
|
@ -39,10 +39,9 @@ const c = process.env.ENABLE_EXPERIMENTAL_CACHE ? new Keyv() : null
|
||||||
const fetchWKD = (id) => {
|
const fetchWKD = (id) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const output = {
|
let publicKey = null
|
||||||
publicKey: null,
|
let profile = null
|
||||||
fetchURL: null
|
let fetchURL = null
|
||||||
}
|
|
||||||
|
|
||||||
if (!id.includes('@')) {
|
if (!id.includes('@')) {
|
||||||
reject(new Error(`The WKD identifier "${id}" is invalid`))
|
reject(new Error(`The WKD identifier "${id}" is invalid`))
|
||||||
|
@ -59,14 +58,14 @@ const fetchWKD = (id) => {
|
||||||
|
|
||||||
const hash = createHash('md5').update(id).digest('hex')
|
const hash = createHash('md5').update(id).digest('hex')
|
||||||
if (c && await c.get(hash)) {
|
if (c && await c.get(hash)) {
|
||||||
plaintext = Uint8Array.from((await c.get(hash)).split(','))
|
profile = doipjs.Claim.fromJson(JSON.parse(await c.get(hash)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!plaintext) {
|
if (!profile) {
|
||||||
try {
|
try {
|
||||||
plaintext = await got(urlAdvanced).then((response) => {
|
plaintext = await got(urlAdvanced).then((response) => {
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
output.fetchURL = urlAdvanced
|
fetchURL = urlAdvanced
|
||||||
return new Uint8Array(response.rawBody)
|
return new Uint8Array(response.rawBody)
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
|
@ -76,7 +75,7 @@ const fetchWKD = (id) => {
|
||||||
try {
|
try {
|
||||||
plaintext = await got(urlDirect).then((response) => {
|
plaintext = await got(urlDirect).then((response) => {
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
output.fetchURL = urlDirect
|
fetchURL = urlDirect
|
||||||
return new Uint8Array(response.rawBody)
|
return new Uint8Array(response.rawBody)
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
|
@ -91,24 +90,29 @@ const fetchWKD = (id) => {
|
||||||
reject(new Error('No public keys could be fetched using WKD'))
|
reject(new Error('No public keys could be fetched using WKD'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c && plaintext instanceof Uint8Array) {
|
|
||||||
await c.set(hash, plaintext.toString(), 60 * 1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
output.publicKey = await readKey({
|
publicKey = await readKey({
|
||||||
binaryKey: plaintext
|
binaryKey: plaintext
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(new Error('No public keys could be read from the data fetched using WKD'))
|
reject(new Error('No public keys could be read from the data fetched using WKD'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!output.publicKey) {
|
if (!publicKey) {
|
||||||
reject(new Error('No public keys could be read from the data fetched using WKD'))
|
reject(new Error('No public keys could be read from the data fetched using WKD'))
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(output)
|
profile = await doipjs.openpgp.parsePublicKey(publicKey)
|
||||||
|
profile.publicKey.fetch.method = 'wkd'
|
||||||
|
profile.publicKey.fetch.query = id
|
||||||
|
profile.publicKey.fetch.resolvedUrl = fetchURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c && plaintext instanceof Uint8Array) {
|
||||||
|
await c.set(hash, JSON.stringify(profile), 60 * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(profile)
|
||||||
})()
|
})()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -116,10 +120,8 @@ const fetchWKD = (id) => {
|
||||||
const fetchHKP = (id, keyserverDomain) => {
|
const fetchHKP = (id, keyserverDomain) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const output = {
|
let profile = null
|
||||||
publicKey: null,
|
let fetchURL = null
|
||||||
fetchURL: null
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyserverDomainNormalized = keyserverDomain || 'keys.openpgp.org'
|
const keyserverDomainNormalized = keyserverDomain || 'keys.openpgp.org'
|
||||||
|
|
||||||
|
@ -135,31 +137,36 @@ const fetchHKP = (id, keyserverDomain) => {
|
||||||
query = `0x${sanitizedId}`
|
query = `0x${sanitizedId}`
|
||||||
}
|
}
|
||||||
|
|
||||||
output.fetchURL = `https://${keyserverDomainNormalized}/pks/lookup?op=get&options=mr&search=${query}`
|
fetchURL = `https://${keyserverDomainNormalized}/pks/lookup?op=get&options=mr&search=${query}`
|
||||||
|
|
||||||
const hash = createHash('md5').update(`${query}__${keyserverDomainNormalized}`).digest('hex')
|
const hash = createHash('md5').update(`${query}__${keyserverDomainNormalized}`).digest('hex')
|
||||||
|
|
||||||
if (c && await c.get(hash)) {
|
if (c && await c.get(hash)) {
|
||||||
output.publicKey = await readKey({
|
profile = doipjs.Claim.fromJson(JSON.parse(await c.get(hash)))
|
||||||
armoredKey: await c.get(hash)
|
}
|
||||||
})
|
|
||||||
} else {
|
if (!profile) {
|
||||||
try {
|
try {
|
||||||
output.publicKey = await doipjs.keys.fetchHKP(query, keyserverDomainNormalized)
|
profile = await doipjs.openpgp.fetchHKP(query, keyserverDomainNormalized)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
profile = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!profile) {
|
||||||
reject(new Error('No public keys could be fetched using HKP'))
|
reject(new Error('No public keys could be fetched using HKP'))
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!output.publicKey) {
|
profile.publicKey.fetch.method = 'hkp'
|
||||||
reject(new Error('No public keys could be fetched using HKP'))
|
profile.publicKey.fetch.query = id
|
||||||
|
profile.publicKey.fetch.resolvedUrl = fetchURL
|
||||||
|
|
||||||
|
if (c && profile instanceof doipjs.Profile) {
|
||||||
|
await c.set(hash, JSON.stringify(profile), 60 * 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c && output.publicKey instanceof PublicKey) {
|
resolve(profile)
|
||||||
await c.set(hash, output.publicKey.armor(), 60 * 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(output)
|
|
||||||
})()
|
})()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -167,11 +174,7 @@ const fetchHKP = (id, keyserverDomain) => {
|
||||||
const fetchSignature = (signature) => {
|
const fetchSignature = (signature) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const output = {
|
let profile = null
|
||||||
publicKey: null,
|
|
||||||
fetchURL: null,
|
|
||||||
keyData: null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check validity of signature
|
// Check validity of signature
|
||||||
let signatureData
|
let signatureData
|
||||||
|
@ -185,30 +188,18 @@ const fetchSignature = (signature) => {
|
||||||
|
|
||||||
// Process the signature
|
// Process the signature
|
||||||
try {
|
try {
|
||||||
output.keyData = await doipjs.signatures.process(signature)
|
profile = await doipjs.signatures.parse(signature)
|
||||||
output.publicKey = output.keyData.key.data
|
|
||||||
// TODO Find the URL to the key
|
// TODO Find the URL to the key
|
||||||
output.fetchURL = null
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(new Error(`Signature could not be properly read (${error.message})`))
|
reject(new Error(`Signature could not be properly read (${error.message})`))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a key was fetched
|
// Check if a key was fetched
|
||||||
if (!output.publicKey) {
|
if (!profile) {
|
||||||
reject(new Error('No public keys could be fetched'))
|
reject(new Error('No profile could be fetched'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check validity of signature
|
resolve(profile)
|
||||||
const verified = await verify({
|
|
||||||
message: signatureData,
|
|
||||||
verificationKeys: output.publicKey
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!await verified.signatures[0].verified) {
|
|
||||||
reject(new Error('Signature was invalid'))
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(output)
|
|
||||||
})()
|
})()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -216,23 +207,24 @@ const fetchSignature = (signature) => {
|
||||||
const fetchKeybase = (username, fingerprint) => {
|
const fetchKeybase = (username, fingerprint) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const output = {
|
let profile = null
|
||||||
publicKey: null,
|
let fetchURL = null
|
||||||
fetchURL: null
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
output.publicKey = await doipjs.keys.fetchKeybase(username, fingerprint)
|
profile = await doipjs.openpgp.fetchKeybase(username, fingerprint)
|
||||||
output.fetchURL = `https://keybase.io/${username}/pgp_keys.asc?fingerprint=${fingerprint}`
|
fetchURL = `https://keybase.io/${username}/pgp_keys.asc?fingerprint=${fingerprint}`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(new Error('No public keys could be fetched from Keybase'))
|
reject(new Error('No public keys could be fetched from Keybase'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!output.publicKey) {
|
if (!profile) {
|
||||||
reject(new Error('No public keys could be fetched from Keybase'))
|
reject(new Error('No public keys could be fetched from Keybase'))
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(output)
|
profile.publicKey.fetch.method = 'http'
|
||||||
|
profile.publicKey.fetch.resolvedUrl = fetchURL
|
||||||
|
|
||||||
|
resolve(profile)
|
||||||
})()
|
})()
|
||||||
})
|
})
|
||||||
}
|
}
|
|
@ -39,7 +39,7 @@ export function generatePageTitle (type, data) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'profile':
|
case 'profile':
|
||||||
try {
|
try {
|
||||||
return `${data.keyData.users[data.keyData.primaryUserIndex].userData.name} - Keyoxide`
|
return `${data.personas[data.primaryPersonaIndex].name} - Keyoxide`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return 'Profile - Keyoxide'
|
return 'Profile - Keyoxide'
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue