Enforce JS code style

This commit is contained in:
Yarmo Mackenbach 2022-11-14 20:42:50 +01:00
parent da57f4c57d
commit f6df547951
No known key found for this signature in database
GPG key ID: 37367F4AF4087AD1
12 changed files with 2062 additions and 1152 deletions

View file

@ -34,164 +34,164 @@ import { generateWKDProfile, generateHKPProfile } from '../../server/index.js'
import 'dotenv/config.js' import 'dotenv/config.js'
const router = express.Router() const router = express.Router()
const ajv = new Ajv({coerceTypes: true}) const ajv = new Ajv({ coerceTypes: true })
const apiProfileSchema = { const apiProfileSchema = {
type: "object", type: 'object',
properties: { properties: {
keyData: { keyData: {
type: "object", type: 'object',
properties: { properties: {
fingerprint: { fingerprint: {
type: "string" type: 'string'
}, },
openpgp4fpr: { openpgp4fpr: {
type: "string" type: 'string'
}, },
users: { users: {
type: "array", type: 'array',
items: { items: {
type: "object", type: 'object',
properties: { properties: {
userData: { userData: {
type: "object", type: 'object',
properties: { properties: {
id: { type: "string" }, id: { type: 'string' },
name: { type: "string" }, name: { type: 'string' },
email: { type: "string" }, email: { type: 'string' },
comment: { type: "string" }, comment: { type: 'string' },
isPrimary: { type: "boolean" }, isPrimary: { type: 'boolean' },
isRevoked: { type: "boolean" }, isRevoked: { type: 'boolean' }
} }
}, },
claims: { claims: {
type: "array", type: 'array',
items: { items: {
type: "object", type: 'object',
properties: { properties: {
claimVersion: { type: "integer" }, claimVersion: { type: 'integer' },
uri: { type: "string" }, uri: { type: 'string' },
fingerprint: { type: "string" }, fingerprint: { type: 'string' },
status: { type: "string" }, status: { type: 'string' },
matches: { matches: {
type: "array", type: 'array',
items: { items: {
type: "object", type: 'object',
properties: { properties: {
serviceProvider: { serviceProvider: {
type: "object", type: 'object',
properties: { properties: {
type: { type: "string" }, type: { type: 'string' },
name: { type: "string" }, name: { type: 'string' }
} }
}, },
match: { match: {
type: "object", type: 'object',
properties: { properties: {
regularExpression: { type: "object" }, regularExpression: { type: 'object' },
isAmbiguous: { type: "boolean" }, isAmbiguous: { type: 'boolean' }
} }
}, },
profile: { profile: {
type: "object", type: 'object',
properties: { properties: {
display: { type: "string" }, display: { type: 'string' },
uri: { type: "string" }, uri: { type: 'string' },
qr: { type: "string" }, qr: { type: 'string' }
} }
}, },
proof: { proof: {
type: "object", type: 'object',
properties: { properties: {
uri: { type: "string" }, uri: { type: 'string' },
request: { request: {
type: "object", type: 'object',
properties: { properties: {
fetcher: { type: "string" }, fetcher: { type: 'string' },
access: { type: "string" }, access: { type: 'string' },
format: { type: "string" }, format: { type: 'string' },
data: { type: "object" }, data: { type: 'object' }
}
} }
},
} }
}, },
claim: { claim: {
type: "object", type: 'object',
properties: { properties: {
format: { type: "string" }, format: { type: 'string' },
relation: { type: "string" }, relation: { type: 'string' },
path: { path: {
type: "array", type: 'array',
items: { items: {
type: "string" type: 'string'
}
}
} }
},
} }
},
} }
} }
}, },
verification: { verification: {
type: "object" type: 'object'
}, },
summary: { summary: {
type: "object", type: 'object',
properties: { properties: {
profileName: { type: "string" }, profileName: { type: 'string' },
profileURL: { type: "string" }, profileURL: { type: 'string' },
serviceProviderName: { type: "string" }, serviceProviderName: { type: 'string' },
isVerificationDone: { type: "boolean" }, isVerificationDone: { type: 'boolean' },
isVerified: { type: "boolean" }, isVerified: { type: 'boolean' }
}
} }
} }
} }
} }
},
} }
} }
}, },
primaryUserIndex: { primaryUserIndex: {
type: "integer" type: 'integer'
}, },
key: { key: {
type: "object", type: 'object',
properties: { properties: {
data: { type: "object" }, data: { type: 'object' },
fetchMethod: { type: "string" }, fetchMethod: { type: 'string' },
uri: { type: "string" }, uri: { type: 'string' }
}
}
} }
}, },
},
},
keyoxide: { keyoxide: {
type: "object", type: 'object',
properties: { properties: {
url: { type: "string" }, url: { type: 'string' }
} }
}, },
extra: { extra: {
type: "object", type: 'object',
properties: { properties: {
avatarURL: { type: "string" }, avatarURL: { type: 'string' }
} }
}, },
errors: { errors: {
type: "array" type: 'array'
}
}, },
}, required: ['keyData', 'keyoxide', 'extra', 'errors'],
required: ["keyData", "keyoxide", "extra", "errors"],
additionalProperties: false additionalProperties: false
} }
const apiProfileValidate = ajv.compile(apiProfileSchema) const apiProfileValidate = ajv.compile(apiProfileSchema)
const doVerification = async (data) => { const doVerification = async (data) => {
let promises = [] const promises = []
let results = [] const results = []
let verificationOptions = { const verificationOptions = {
proxy: { proxy: {
hostname: process.env.PROXY_HOSTNAME, hostname: process.env.PROXY_HOSTNAME,
policy: (process.env.PROXY_HOSTNAME != "") ? 'adaptive' : 'never' policy: (process.env.PROXY_HOSTNAME !== '') ? 'adaptive' : 'never'
} }
} }
@ -202,10 +202,12 @@ const doVerification = async (data) => {
const claim = user.claims[iClaim] const claim = user.claims[iClaim]
promises.push( promises.push(
new Promise(async (resolve, reject) => { new Promise((resolve, reject) => {
(async () => {
await claim.verify(verificationOptions) await claim.verify(verificationOptions)
results.push([iUser, iClaim, claim]) results.push([iUser, iClaim, claim])
resolve() resolve()
})()
}) })
) )
} }
@ -220,8 +222,6 @@ const doVerification = async (data) => {
} }
const sanitize = (data) => { const sanitize = (data) => {
let results = []
const dataClone = JSON.parse(JSON.stringify(data)) const dataClone = JSON.parse(JSON.stringify(data))
for (let iUser = 0; iUser < dataClone.keyData.users.length; iUser++) { for (let iUser = 0; iUser < dataClone.keyData.users.length; iUser++) {
@ -232,7 +232,7 @@ const sanitize = (data) => {
// TODO Fix upstream // TODO Fix upstream
for (let iMatch = 0; iMatch < claim.matches.length; iMatch++) { for (let iMatch = 0; iMatch < claim.matches.length; iMatch++) {
const match = claim.matches[iMatch]; const match = claim.matches[iMatch]
if (Array.isArray(match.claim)) { if (Array.isArray(match.claim)) {
match.claim = match.claim[0] match.claim = match.claim[0]
} }
@ -254,7 +254,7 @@ const sanitize = (data) => {
const valid = apiProfileValidate(data) const valid = apiProfileValidate(data)
if (!valid) { if (!valid) {
throw new Error(`Profile data sanitization error`) throw new Error('Profile data sanitization error')
} }
return data return data
@ -268,7 +268,7 @@ const addSummaryToClaims = (data) => {
for (let claimIndex = 0; claimIndex < user.claims.length; claimIndex++) { for (let claimIndex = 0; claimIndex < user.claims.length; claimIndex++) {
const claim = user.claims[claimIndex] const claim = user.claims[claimIndex]
const isVerificationDone = claim.status === "verified" const isVerificationDone = claim.status === 'verified'
const isVerified = isVerificationDone ? claim.verification.result : false const isVerified = isVerificationDone ? claim.verification.result : false
const isAmbiguous = isVerified const isAmbiguous = isVerified
? false ? false
@ -276,10 +276,10 @@ const addSummaryToClaims = (data) => {
data.keyData.users[userIndex].claims[claimIndex].summary = { data.keyData.users[userIndex].claims[claimIndex].summary = {
profileName: !isAmbiguous ? claim.matches[0].profile.display : claim.uri, profileName: !isAmbiguous ? claim.matches[0].profile.display : claim.uri,
profileURL: !isAmbiguous ? claim.matches[0].profile.uri : "", profileURL: !isAmbiguous ? claim.matches[0].profile.uri : '',
serviceProviderName: !isAmbiguous ? claim.matches[0].serviceprovider.name : "", serviceProviderName: !isAmbiguous ? claim.matches[0].serviceprovider.name : '',
isVerificationDone: isVerificationDone, isVerificationDone,
isVerified: isVerified, isVerified
} }
} }
} }
@ -289,11 +289,11 @@ const addSummaryToClaims = (data) => {
router.get('/profile/fetch', router.get('/profile/fetch',
check('query').exists(), check('query').exists(),
check('protocol').optional().toLowerCase().isIn(["hkp", "wkd"]), check('protocol').optional().toLowerCase().isIn(['hkp', 'wkd']),
check('doVerification').default(false).isBoolean().toBoolean(), check('doVerification').default(false).isBoolean().toBoolean(),
check('returnPublicKey').default(false).isBoolean().toBoolean(), check('returnPublicKey').default(false).isBoolean().toBoolean(),
async (req, res) => { async (req, res) => {
const valRes = validationResult(req); const valRes = validationResult(req)
if (!valRes.isEmpty()) { if (!valRes.isEmpty()) {
res.status(400).send(valRes) res.status(400).send(valRes)
return return
@ -304,17 +304,17 @@ router.get('/profile/fetch',
switch (req.query.protocol) { switch (req.query.protocol) {
case 'wkd': case 'wkd':
data = await generateWKDProfile(req.query.query) data = await generateWKDProfile(req.query.query)
break; break
case 'hkp': case 'hkp':
data = await generateHKPProfile(req.query.query) data = await generateHKPProfile(req.query.query)
break; break
default: default:
if (req.query.query.includes('@')) { if (req.query.query.includes('@')) {
data = await generateWKDProfile(req.query.query) data = await generateWKDProfile(req.query.query)
} else { } else {
data = await generateHKPProfile(req.query.query) data = await generateHKPProfile(req.query.query)
} }
break; break
} }
if (data.errors.length > 0) { if (data.errors.length > 0) {
@ -364,11 +364,11 @@ router.get('/profile/verify',
} }
// Do verification // Do verification
data = await doVerification(req.query.data) let data = await doVerification(req.query.data)
try { try {
// Sanitize JSON // Sanitize JSON
data = sanitize(data); data = sanitize(data)
} catch (error) { } catch (error) {
data.keyData = {} data.keyData = {}
data.extra = {} data.extra = {}

View file

@ -34,164 +34,164 @@ import { generateWKDProfile, generateHKPProfile } from '../../server/index.js'
import 'dotenv/config.js' import 'dotenv/config.js'
const router = express.Router() const router = express.Router()
const ajv = new Ajv({coerceTypes: true}) const ajv = new Ajv({ coerceTypes: true })
const apiProfileSchema = { const apiProfileSchema = {
type: "object", type: 'object',
properties: { properties: {
keyData: { keyData: {
type: "object", type: 'object',
properties: { properties: {
fingerprint: { fingerprint: {
type: "string" type: 'string'
}, },
openpgp4fpr: { openpgp4fpr: {
type: "string" type: 'string'
}, },
users: { users: {
type: "array", type: 'array',
items: { items: {
type: "object", type: 'object',
properties: { properties: {
userData: { userData: {
type: "object", type: 'object',
properties: { properties: {
id: { type: "string" }, id: { type: 'string' },
name: { type: "string" }, name: { type: 'string' },
email: { type: "string" }, email: { type: 'string' },
comment: { type: "string" }, comment: { type: 'string' },
isPrimary: { type: "boolean" }, isPrimary: { type: 'boolean' },
isRevoked: { type: "boolean" }, isRevoked: { type: 'boolean' }
} }
}, },
claims: { claims: {
type: "array", type: 'array',
items: { items: {
type: "object", type: 'object',
properties: { properties: {
claimVersion: { type: "integer" }, claimVersion: { type: 'integer' },
uri: { type: "string" }, uri: { type: 'string' },
fingerprint: { type: "string" }, fingerprint: { type: 'string' },
status: { type: "string" }, status: { type: 'string' },
matches: { matches: {
type: "array", type: 'array',
items: { items: {
type: "object", type: 'object',
properties: { properties: {
serviceProvider: { serviceProvider: {
type: "object", type: 'object',
properties: { properties: {
type: { type: "string" }, type: { type: 'string' },
name: { type: "string" }, name: { type: 'string' }
} }
}, },
match: { match: {
type: "object", type: 'object',
properties: { properties: {
regularExpression: { type: "object" }, regularExpression: { type: 'object' },
isAmbiguous: { type: "boolean" }, isAmbiguous: { type: 'boolean' }
} }
}, },
profile: { profile: {
type: "object", type: 'object',
properties: { properties: {
display: { type: "string" }, display: { type: 'string' },
uri: { type: "string" }, uri: { type: 'string' },
qr: { type: "string" }, qr: { type: 'string' }
} }
}, },
proof: { proof: {
type: "object", type: 'object',
properties: { properties: {
uri: { type: "string" }, uri: { type: 'string' },
request: { request: {
type: "object", type: 'object',
properties: { properties: {
fetcher: { type: "string" }, fetcher: { type: 'string' },
access: { type: "string" }, access: { type: 'string' },
format: { type: "string" }, format: { type: 'string' },
data: { type: "object" }, data: { type: 'object' }
}
} }
},
} }
}, },
claim: { claim: {
type: "object", type: 'object',
properties: { properties: {
format: { type: "string" }, format: { type: 'string' },
relation: { type: "string" }, relation: { type: 'string' },
path: { path: {
type: "array", type: 'array',
items: { items: {
type: "string" type: 'string'
}
}
} }
},
} }
},
} }
} }
}, },
verification: { verification: {
type: "object" type: 'object'
}, },
summary: { summary: {
type: "object", type: 'object',
properties: { properties: {
profileName: { type: "string" }, profileName: { type: 'string' },
profileURL: { type: "string" }, profileURL: { type: 'string' },
serviceProviderName: { type: "string" }, serviceProviderName: { type: 'string' },
isVerificationDone: { type: "boolean" }, isVerificationDone: { type: 'boolean' },
isVerified: { type: "boolean" }, isVerified: { type: 'boolean' }
}
} }
} }
} }
} }
},
} }
} }
}, },
primaryUserIndex: { primaryUserIndex: {
type: "integer" type: 'integer'
}, },
key: { key: {
type: "object", type: 'object',
properties: { properties: {
data: { type: "object" }, data: { type: 'object' },
fetchMethod: { type: "string" }, fetchMethod: { type: 'string' },
uri: { type: "string" }, uri: { type: 'string' }
}
}
} }
}, },
},
},
keyoxide: { keyoxide: {
type: "object", type: 'object',
properties: { properties: {
url: { type: "string" }, url: { type: 'string' }
} }
}, },
extra: { extra: {
type: "object", type: 'object',
properties: { properties: {
avatarURL: { type: "string" }, avatarURL: { type: 'string' }
} }
}, },
errors: { errors: {
type: "array" type: 'array'
}
}, },
}, required: ['keyData', 'keyoxide', 'extra', 'errors'],
required: ["keyData", "keyoxide", "extra", "errors"],
additionalProperties: false additionalProperties: false
} }
const apiProfileValidate = ajv.compile(apiProfileSchema) const apiProfileValidate = ajv.compile(apiProfileSchema)
const doVerification = async (data) => { const doVerification = async (data) => {
let promises = [] const promises = []
let results = [] const results = []
let verificationOptions = { const verificationOptions = {
proxy: { proxy: {
hostname: process.env.PROXY_HOSTNAME, hostname: process.env.PROXY_HOSTNAME,
policy: (process.env.PROXY_HOSTNAME != "") ? 'adaptive' : 'never' policy: (process.env.PROXY_HOSTNAME !== '') ? 'adaptive' : 'never'
} }
} }
@ -202,10 +202,12 @@ const doVerification = async (data) => {
const claim = user.claims[iClaim] const claim = user.claims[iClaim]
promises.push( promises.push(
new Promise(async (resolve, reject) => { new Promise((resolve, reject) => {
(async () => {
await claim.verify(verificationOptions) await claim.verify(verificationOptions)
results.push([iUser, iClaim, claim]) results.push([iUser, iClaim, claim])
resolve() resolve()
})()
}) })
) )
} }
@ -220,8 +222,6 @@ const doVerification = async (data) => {
} }
const sanitize = (data) => { const sanitize = (data) => {
let results = []
const dataClone = JSON.parse(JSON.stringify(data)) const dataClone = JSON.parse(JSON.stringify(data))
for (let iUser = 0; iUser < dataClone.keyData.users.length; iUser++) { for (let iUser = 0; iUser < dataClone.keyData.users.length; iUser++) {
@ -232,7 +232,7 @@ const sanitize = (data) => {
// TODO Fix upstream // TODO Fix upstream
for (let iMatch = 0; iMatch < claim.matches.length; iMatch++) { for (let iMatch = 0; iMatch < claim.matches.length; iMatch++) {
const match = claim.matches[iMatch]; const match = claim.matches[iMatch]
if (Array.isArray(match.claim)) { if (Array.isArray(match.claim)) {
match.claim = match.claim[0] match.claim = match.claim[0]
} }
@ -254,7 +254,7 @@ const sanitize = (data) => {
const valid = apiProfileValidate(data) const valid = apiProfileValidate(data)
if (!valid) { if (!valid) {
throw new Error(`Profile data sanitization error`) throw new Error('Profile data sanitization error')
} }
return data return data
@ -268,7 +268,7 @@ const addSummaryToClaims = (data) => {
for (let claimIndex = 0; claimIndex < user.claims.length; claimIndex++) { for (let claimIndex = 0; claimIndex < user.claims.length; claimIndex++) {
const claim = user.claims[claimIndex] const claim = user.claims[claimIndex]
const isVerificationDone = claim.status === "verified" const isVerificationDone = claim.status === 'verified'
const isVerified = isVerificationDone ? claim.verification.result : false const isVerified = isVerificationDone ? claim.verification.result : false
const isAmbiguous = isVerified const isAmbiguous = isVerified
? false ? false
@ -276,10 +276,10 @@ const addSummaryToClaims = (data) => {
data.keyData.users[userIndex].claims[claimIndex].summary = { data.keyData.users[userIndex].claims[claimIndex].summary = {
profileName: !isAmbiguous ? claim.matches[0].profile.display : claim.uri, profileName: !isAmbiguous ? claim.matches[0].profile.display : claim.uri,
profileURL: !isAmbiguous ? claim.matches[0].profile.uri : "", profileURL: !isAmbiguous ? claim.matches[0].profile.uri : '',
serviceProviderName: !isAmbiguous ? claim.matches[0].serviceprovider.name : "", serviceProviderName: !isAmbiguous ? claim.matches[0].serviceprovider.name : '',
isVerificationDone: isVerificationDone, isVerificationDone,
isVerified: isVerified, isVerified
} }
} }
} }
@ -289,11 +289,11 @@ const addSummaryToClaims = (data) => {
router.get('/fetch', router.get('/fetch',
check('query').exists(), check('query').exists(),
check('protocol').optional().toLowerCase().isIn(["hkp", "wkd"]), check('protocol').optional().toLowerCase().isIn(['hkp', 'wkd']),
check('doVerification').default(false).isBoolean().toBoolean(), check('doVerification').default(false).isBoolean().toBoolean(),
check('returnPublicKey').default(false).isBoolean().toBoolean(), check('returnPublicKey').default(false).isBoolean().toBoolean(),
async (req, res) => { async (req, res) => {
const valRes = validationResult(req); const valRes = validationResult(req)
if (!valRes.isEmpty()) { if (!valRes.isEmpty()) {
res.status(400).send(valRes) res.status(400).send(valRes)
return return
@ -304,17 +304,17 @@ router.get('/fetch',
switch (req.query.protocol) { switch (req.query.protocol) {
case 'wkd': case 'wkd':
data = await generateWKDProfile(req.query.query) data = await generateWKDProfile(req.query.query)
break; break
case 'hkp': case 'hkp':
data = await generateHKPProfile(req.query.query) data = await generateHKPProfile(req.query.query)
break; break
default: default:
if (req.query.query.includes('@')) { if (req.query.query.includes('@')) {
data = await generateWKDProfile(req.query.query) data = await generateWKDProfile(req.query.query)
} else { } else {
data = await generateHKPProfile(req.query.query) data = await generateHKPProfile(req.query.query)
} }
break; break
} }
if (data.errors.length > 0) { if (data.errors.length > 0) {
@ -364,11 +364,11 @@ router.get('/verify',
} }
// Do verification // Do verification
data = await doVerification(req.query.data) let data = await doVerification(req.query.data)
try { try {
// Sanitize JSON // Sanitize JSON
data = sanitize(data); data = sanitize(data)
} catch (error) { } catch (error) {
data.keyData = {} data.keyData = {}
data.extra = {} data.extra = {}

View file

@ -34,6 +34,7 @@
"mini-css-extract-plugin": "^2.5.3", "mini-css-extract-plugin": "^2.5.3",
"mocha": "^9.2.1", "mocha": "^9.2.1",
"nodemon": "^2.0.19", "nodemon": "^2.0.19",
"standard": "^17.0.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"webpack": "^5.69.1", "webpack": "^5.69.1",
"webpack-bundle-analyzer": "^4.5.0", "webpack-bundle-analyzer": "^4.5.0",
@ -42,12 +43,14 @@
"scripts": { "scripts": {
"start": "node --experimental-fetch ./", "start": "node --experimental-fetch ./",
"dev": "yarn run watch & yarn run build:static:dev", "dev": "yarn run watch & yarn run build:static:dev",
"test": "mocha", "test": "yarn run standard:check && mocha",
"watch": "./node_modules/.bin/nodemon --config nodemon.json ./", "watch": "./node_modules/.bin/nodemon --config nodemon.json ./",
"build": "yarn run build:server & yarn run build:static", "build": "yarn run build:server & yarn run build:static",
"build:server": "ncc build ./src/index.js -e jsdom -o dist", "build:server": "ncc build ./src/index.js -e jsdom -o dist",
"build:static": "webpack --config webpack.config.js --env static=true --env mode=production", "build:static": "webpack --config webpack.config.js --env static=true --env mode=production",
"build:static:dev": "webpack --config webpack.config.js --env static=true --env mode=development", "build:static:dev": "webpack --config webpack.config.js --env static=true --env mode=development",
"standard:check": "./node_modules/.bin/standard ./src ./api ./routes ./server",
"standard:fix": "./node_modules/.bin/standard --fix ./src ./api ./routes ./server",
"license:check": "./node_modules/.bin/license-check-and-add check", "license:check": "./node_modules/.bin/license-check-and-add check",
"license:add": "./node_modules/.bin/license-check-and-add add", "license:add": "./node_modules/.bin/license-check-and-add add",
"license:remove": "./node_modules/.bin/license-check-and-add remove" "license:remove": "./node_modules/.bin/license-check-and-add remove"

View file

@ -33,28 +33,28 @@ import { readFileSync } from 'fs'
import demoData from '../server/demo.js' import demoData from '../server/demo.js'
const router = express.Router() const router = express.Router()
const md = markdownImport({typographer: true}) const md = markdownImport({ typographer: true })
router.get('/', (req, res) => { router.get('/', (req, res) => {
let highlights = [] const highlights = []
for (let index = 1; index < 4; index++) { for (let index = 1; index < 4; index++) {
if (process.env[`KX_HIGHLIGHTS_${index}_NAME`] if (process.env[`KX_HIGHLIGHTS_${index}_NAME`] &&
&& process.env[`KX_HIGHLIGHTS_${index}_FINGERPRINT`]) { process.env[`KX_HIGHLIGHTS_${index}_FINGERPRINT`]) {
highlights.push({ highlights.push({
name: process.env[`KX_HIGHLIGHTS_${index}_NAME`], name: process.env[`KX_HIGHLIGHTS_${index}_NAME`],
description: process.env[`KX_HIGHLIGHTS_${index}_DESCRIPTION`], description: process.env[`KX_HIGHLIGHTS_${index}_DESCRIPTION`],
fingerprint: process.env[`KX_HIGHLIGHTS_${index}_FINGERPRINT`], fingerprint: process.env[`KX_HIGHLIGHTS_${index}_FINGERPRINT`]
}) })
} }
} }
res.render('index', { highlights: highlights, demoData: demoData }) res.render('index', { highlights, demoData })
}) })
router.get('/privacy', (req, res) => { router.get('/privacy', (req, res) => {
let rawContent = readFileSync(`./content/privacy-policy.md`, "utf8") const rawContent = readFileSync('./content/privacy-policy.md', 'utf8')
const content = md.render(rawContent) const content = md.render(rawContent)
res.render(`article`, { title: `Privacy policy`, content: content }) res.render('article', { title: 'Privacy policy', content })
}) })
router.get('/.well-known/webfinger', (req, res) => { router.get('/.well-known/webfinger', (req, res) => {
@ -64,12 +64,12 @@ router.get('/.well-known/webfinger', (req, res) => {
} }
const body = { const body = {
'subject': `acct:keyoxide@${process.env.DOMAIN}`, subject: `acct:keyoxide@${process.env.DOMAIN}`,
'aliases': [`https://${process.env.DOMAIN}/users/keyoxide`], aliases: [`https://${process.env.DOMAIN}/users/keyoxide`],
'links': [{ links: [{
'rel': 'self', rel: 'self',
'type': 'application/activity+json', type: 'application/activity+json',
'href': `https://${process.env.DOMAIN}/users/keyoxide` href: `https://${process.env.DOMAIN}/users/keyoxide`
}] }]
} }
res.json(body) res.json(body)
@ -86,14 +86,14 @@ router.get('/users/keyoxide', (req, res) => {
'https://www.w3.org/ns/activitystreams', 'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1' 'https://w3id.org/security/v1'
], ],
'id': `https://${process.env.DOMAIN}/users/keyoxide`, id: `https://${process.env.DOMAIN}/users/keyoxide`,
'type': 'Application', type: 'Application',
'inbox': `https://${process.env.DOMAIN}/users/keyoxide/inbox`, inbox: `https://${process.env.DOMAIN}/users/keyoxide/inbox`,
'preferredUsername': `${process.env.DOMAIN}`, preferredUsername: `${process.env.DOMAIN}`,
'publicKey': { publicKey: {
'id': `https://${process.env.DOMAIN}/users/keyoxide#main-key`, id: `https://${process.env.DOMAIN}/users/keyoxide#main-key`,
'owner': `https://${process.env.DOMAIN}/users/keyoxide`, owner: `https://${process.env.DOMAIN}/users/keyoxide`,
'publicKeyPem': `${process.env.ACTIVITYPUB_PUBLIC_KEY}` publicKeyPem: `${process.env.ACTIVITYPUB_PUBLIC_KEY}`
} }
} }
res.type('application/activity+json').json(body) res.type('application/activity+json').json(body)

View file

@ -42,42 +42,42 @@ 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.keyData.openpgp4fpr)
res.render('profile', { title: title, data: data, isSignature: true, signature: req.body.signature }) res.render('profile', { title, data, isSignature: true, signature: req.body.signature })
}) })
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.keyData.openpgp4fpr)
res.render('profile', { title: title, data: data }) res.render('profile', { title, data })
}) })
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.keyData.openpgp4fpr)
res.render('profile', { title: title, data: data }) res.render('profile', { title, data })
}) })
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.keyData.openpgp4fpr)
res.render('profile', { title: title, data: data }) res.render('profile', { title, data })
}) })
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.keyData.openpgp4fpr)
res.render('profile', { title: title, data: data }) res.render('profile', { title, data })
}) })
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.keyData.openpgp4fpr)
res.render('profile', { title: title, data: data }) res.render('profile', { title, data })
}) })
export default router export default router

View file

@ -31,48 +31,48 @@ import express from 'express'
const router = express.Router() const router = express.Router()
router.get('/', function(req, res) { router.get('/', function (req, res) {
res.render('util/index') res.render('util/index')
}) })
router.get('/profile-url', function(req, res) { router.get('/profile-url', function (req, res) {
res.render('util/profile-url') res.render('util/profile-url')
}) })
router.get('/profile-url/:input', function(req, res) { router.get('/profile-url/:input', function (req, res) {
res.render('util/profile-url', { input: req.params.input }) res.render('util/profile-url', { input: req.params.input })
}) })
router.get('/qr', function(req, res) { router.get('/qr', function (req, res) {
res.render('util/qr') res.render('util/qr')
}) })
router.get('/qr/:input', function(req, res) { router.get('/qr/:input', function (req, res) {
res.render('util/qr', { input: req.params.input }) res.render('util/qr', { input: req.params.input })
}) })
router.get('/qrfp', function(req, res) { router.get('/qrfp', function (req, res) {
res.render('util/qrfp') res.render('util/qrfp')
}) })
router.get('/qrfp/:input', function(req, res) { router.get('/qrfp/:input', function (req, res) {
res.render('util/qrfp', { input: req.params.input }) res.render('util/qrfp', { input: req.params.input })
}) })
router.get('/wkd', function(req, res) { router.get('/wkd', function (req, res) {
res.render('util/wkd') res.render('util/wkd')
}) })
router.get('/wkd/:input', function(req, res) { router.get('/wkd/:input', function (req, res) {
res.render('util/wkd', { input: req.params.input }) res.render('util/wkd', { input: req.params.input })
}) })
router.get('/argon2', function(req, res) { router.get('/argon2', function (req, res) {
res.render('util/argon2') res.render('util/argon2')
}) })
router.get('/argon2/:input', function(req, res) { router.get('/argon2/:input', function (req, res) {
res.render('util/argon2', { input: req.params.input }) res.render('util/argon2', { input: req.params.input })
}) })
router.get('/bcrypt', function(req, res) { router.get('/bcrypt', function (req, res) {
res.render('util/bcrypt') res.render('util/bcrypt')
}) })
router.get('/bcrypt/:input', function(req, res) { router.get('/bcrypt/:input', function (req, res) {
res.render('util/bcrypt', { input: req.params.input }) res.render('util/bcrypt', { input: req.params.input })
}) })

View file

@ -28,54 +28,54 @@ 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/>.
*/ */
export default { export default {
"claimVersion": 1, claimVersion: 1,
"uri": "https://fosstodon.org/@keyoxide", uri: 'https://fosstodon.org/@keyoxide',
"fingerprint": "9f0048ac0b23301e1f77e994909f6bd6f80f485d", fingerprint: '9f0048ac0b23301e1f77e994909f6bd6f80f485d',
"status": "verified", status: 'verified',
"matches": [ matches: [
{ {
"serviceprovider": { serviceprovider: {
"type": "web", type: 'web',
"name": "mastodon (demo)" name: 'mastodon (demo)'
}, },
"match": { match: {
"regularExpression": {}, regularExpression: {},
"isAmbiguous": true isAmbiguous: true
}, },
"profile": { profile: {
"display": "@keyoxide@fosstodon.org", display: '@keyoxide@fosstodon.org',
"uri": "https://fosstodon.org/@keyoxide", uri: 'https://fosstodon.org/@keyoxide',
"qr": null qr: null
}, },
"proof": { proof: {
"uri": "https://fosstodon.org/@keyoxide", uri: 'https://fosstodon.org/@keyoxide',
"request": { request: {
"fetcher": "http", fetcher: 'http',
"access": 0, access: 0,
"format": "json", format: 'json',
"data": { data: {
"url": "https://fosstodon.org/@keyoxide", url: 'https://fosstodon.org/@keyoxide',
"format": "json" format: 'json'
} }
} }
}, },
"claim": { claim: {
"format": 1, format: 1,
"relation": 0, relation: 0,
"path": [ path: [
"attachment", 'attachment',
"value" 'value'
] ]
} }
} }
], ],
"verification": { verification: {
"result": true, result: true,
"completed": true, completed: true,
"errors": [], errors: [],
"proof": { proof: {
"fetcher": "http", fetcher: 'http',
"viaProxy": false viaProxy: false
} }
} }
} }

View file

@ -41,12 +41,12 @@ const generateWKDProfile = async (id) => {
keyData.key.data = {} keyData.key.data = {}
keyData = processKeyData(keyData) keyData = processKeyData(keyData)
let keyoxideData = {} const keyoxideData = {}
keyoxideData.url = `https://${process.env.DOMAIN}/wkd/${id}` keyoxideData.url = `https://${process.env.DOMAIN}/wkd/${id}`
return { return {
key: key, key,
keyData: keyData, keyData,
keyoxide: keyoxideData, keyoxide: keyoxideData,
extra: await computeExtraData(key, keyData), extra: await computeExtraData(key, keyData),
errors: [] errors: []
@ -73,7 +73,7 @@ const generateHKPProfile = async (id, keyserverDomain) => {
keyData.key.data = {} keyData.key.data = {}
keyData = processKeyData(keyData) keyData = processKeyData(keyData)
let keyoxideData = {} const keyoxideData = {}
if (!keyserverDomain || keyserverDomain === 'keys.openpgp.org') { if (!keyserverDomain || keyserverDomain === 'keys.openpgp.org') {
keyoxideData.url = `https://${process.env.DOMAIN}/hkp/${id}` keyoxideData.url = `https://${process.env.DOMAIN}/hkp/${id}`
} else { } else {
@ -81,8 +81,8 @@ const generateHKPProfile = async (id, keyserverDomain) => {
} }
return { return {
key: key, key,
keyData: keyData, keyData,
keyoxide: keyoxideData, keyoxide: keyoxideData,
extra: await computeExtraData(key, keyData), extra: await computeExtraData(key, keyData),
errors: [] errors: []
@ -120,7 +120,7 @@ const generateAutoProfile = async (id) => {
keyData: {}, keyData: {},
keyoxide: {}, keyoxide: {},
extra: {}, extra: {},
errors: ["No public keys could be found"] errors: ['No public keys could be found']
} }
} }
@ -133,11 +133,11 @@ const generateSignatureProfile = async (signature) => {
keyData.key.data = {} keyData.key.data = {}
keyData = processKeyData(keyData) keyData = processKeyData(keyData)
let keyoxideData = {} const keyoxideData = {}
return { return {
key: key, key,
keyData: keyData, keyData,
keyoxide: keyoxideData, keyoxide: keyoxideData,
extra: await computeExtraData(key, keyData), extra: await computeExtraData(key, keyData),
errors: [] errors: []
@ -164,12 +164,12 @@ const generateKeybaseProfile = async (username, fingerprint) => {
keyData.key.data = {} keyData.key.data = {}
keyData = processKeyData(keyData) keyData = processKeyData(keyData)
let keyoxideData = {} const keyoxideData = {}
keyoxideData.url = `https://${process.env.DOMAIN}/keybase/${username}/${fingerprint}` keyoxideData.url = `https://${process.env.DOMAIN}/keybase/${username}/${fingerprint}`
return { return {
key: key, key,
keyData: keyData, keyData,
keyoxide: keyoxideData, keyoxide: keyoxideData,
extra: await computeExtraData(key, keyData), extra: await computeExtraData(key, keyData),
errors: [] errors: []
@ -199,9 +199,9 @@ const processKeyData = (keyData) => {
}) })
// Sort claims // Sort claims
user.claims.sort((a,b) => { user.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].serviceprovider.name < b.matches[0].serviceprovider.name) {
return -1 return -1

View file

@ -37,19 +37,20 @@ import Keyv from 'keyv'
const c = process.env.ENABLE_EXPERIMENTAL_CACHE ? new Keyv() : null const c = process.env.ENABLE_EXPERIMENTAL_CACHE ? new Keyv() : null
const fetchWKD = (id) => { const fetchWKD = (id) => {
return new Promise(async (resolve, reject) => { return new Promise((resolve, reject) => {
let output = { (async () => {
const output = {
publicKey: null, publicKey: null,
fetchURL: null 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`))
} }
const [, localPart, domain] = /([^\@]*)@(.*)/.exec(id) const [, localPart, domain] = /([^@]*)@(.*)/.exec(id)
if (!localPart || !domain) { if (!localPart || !domain) {
reject(new Error(`The WKD identifier "${id}" is invalid`)); reject(new Error(`The WKD identifier "${id}" is invalid`))
} }
const localEncoded = await computeWKDLocalPart(localPart) const localEncoded = await computeWKDLocalPart(localPart)
const urlAdvanced = `https://openpgpkey.${domain}/.well-known/openpgpkey/${domain}/hu/${localEncoded}` const urlAdvanced = `https://openpgpkey.${domain}/.well-known/openpgpkey/${domain}/hu/${localEncoded}`
@ -82,12 +83,12 @@ const fetchWKD = (id) => {
} }
}) })
} catch (error) { } catch (error) {
reject(new Error(`No public keys could be fetched using WKD`)) reject(new Error('No public keys could be fetched using WKD'))
} }
} }
if (!plaintext) { if (!plaintext) {
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) { if (c && plaintext instanceof Uint8Array) {
@ -99,26 +100,28 @@ const fetchWKD = (id) => {
output.publicKey = await readKey({ output.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 (!output.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) resolve(output)
})()
}) })
} }
const fetchHKP = (id, keyserverDomain) => { const fetchHKP = (id, keyserverDomain) => {
return new Promise(async (resolve, reject) => { return new Promise((resolve, reject) => {
let output = { (async () => {
const output = {
publicKey: null, publicKey: null,
fetchURL: null fetchURL: null
} }
keyserverDomain = keyserverDomain ? keyserverDomain : 'keys.openpgp.org' keyserverDomain = keyserverDomain || 'keys.openpgp.org'
let query = '' let query = ''
if (id.includes('@')) { if (id.includes('@')) {
@ -138,13 +141,13 @@ const fetchHKP = (id, keyserverDomain) => {
} else { } else {
try { try {
output.publicKey = await doipjs.keys.fetchHKP(id, keyserverDomain) output.publicKey = await doipjs.keys.fetchHKP(id, keyserverDomain)
} catch(error) { } catch (error) {
reject(new Error(`No public keys could be fetched using HKP`)) reject(new Error('No public keys could be fetched using HKP'))
} }
} }
if (!output.publicKey) { if (!output.publicKey) {
reject(new Error(`No public keys could be fetched using HKP`)) reject(new Error('No public keys could be fetched using HKP'))
} }
if (c && output.publicKey instanceof PublicKey) { if (c && output.publicKey instanceof PublicKey) {
@ -152,12 +155,14 @@ const fetchHKP = (id, keyserverDomain) => {
} }
resolve(output) resolve(output)
})()
}) })
} }
const fetchSignature = (signature) => { const fetchSignature = (signature) => {
return new Promise(async (resolve, reject) => { return new Promise((resolve, reject) => {
let output = { (async () => {
const output = {
publicKey: null, publicKey: null,
fetchURL: null, fetchURL: null,
keyData: null keyData: null
@ -179,13 +184,13 @@ const fetchSignature = (signature) => {
output.publicKey = output.keyData.key.data output.publicKey = output.keyData.key.data
// TODO Find the URL to the key // TODO Find the URL to the key
output.fetchURL = null 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 (!output.publicKey) {
reject(new Error(`No public keys could be fetched`)) reject(new Error('No public keys could be fetched'))
} }
// Check validity of signature // Check validity of signature
@ -199,12 +204,14 @@ const fetchSignature = (signature) => {
} }
resolve(output) resolve(output)
})()
}) })
} }
const fetchKeybase = (username, fingerprint) => { const fetchKeybase = (username, fingerprint) => {
return new Promise(async (resolve, reject) => { return new Promise((resolve, reject) => {
let output = { (async () => {
const output = {
publicKey: null, publicKey: null,
fetchURL: null fetchURL: null
} }
@ -212,15 +219,16 @@ const fetchKeybase = (username, fingerprint) => {
try { try {
output.publicKey = await doipjs.keys.fetchKeybase(username, fingerprint) output.publicKey = await doipjs.keys.fetchKeybase(username, fingerprint)
output.fetchURL = `https://keybase.io/${username}/pgp_keys.asc?fingerprint=${fingerprint}` output.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 (!output.publicKey) {
reject(new Error(`No public keys could be fetched from Keybase`)) reject(new Error('No public keys could be fetched from Keybase'))
} }
resolve(output) resolve(output)
})()
}) })
} }

View file

@ -29,13 +29,13 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
*/ */
import { webcrypto as crypto } from 'crypto' import { webcrypto as crypto } from 'crypto'
export async function computeWKDLocalPart(localPart) { export async function computeWKDLocalPart (localPart) {
const localPartEncoded = new TextEncoder().encode(localPart.toLowerCase()); const localPartEncoded = new TextEncoder().encode(localPart.toLowerCase())
const localPartHashed = new Uint8Array(await crypto.subtle.digest('SHA-1', localPartEncoded)); const localPartHashed = new Uint8Array(await crypto.subtle.digest('SHA-1', localPartEncoded))
return encodeZBase32(localPartHashed); return encodeZBase32(localPartHashed)
} }
export function generatePageTitle(type, data) { export function generatePageTitle (type, data) {
switch (type) { switch (type) {
case 'profile': case 'profile':
try { try {
@ -43,40 +43,38 @@ export function generatePageTitle(type, data) {
} catch (error) { } catch (error) {
return 'Profile - Keyoxide' return 'Profile - Keyoxide'
} }
break
default: default:
return 'Keyoxide' return 'Keyoxide'
break
} }
} }
// Copied from https://github.com/openpgpjs/wkd-client/blob/0d074519e011a5139a8953679cf5f807e4cd2378/src/wkd.js // Copied from https://github.com/openpgpjs/wkd-client/blob/0d074519e011a5139a8953679cf5f807e4cd2378/src/wkd.js
export function encodeZBase32(data) { export function encodeZBase32 (data) {
if (data.length === 0) { if (data.length === 0) {
return ""; return ''
} }
const ALPHABET = "ybndrfg8ejkmcpqxot1uwisza345h769"; const ALPHABET = 'ybndrfg8ejkmcpqxot1uwisza345h769'
const SHIFT = 5; const SHIFT = 5
const MASK = 31; const MASK = 31
let buffer = data[0]; let buffer = data[0]
let index = 1; let index = 1
let bitsLeft = 8; let bitsLeft = 8
let result = ''; let result = ''
while (bitsLeft > 0 || index < data.length) { while (bitsLeft > 0 || index < data.length) {
if (bitsLeft < SHIFT) { if (bitsLeft < SHIFT) {
if (index < data.length) { if (index < data.length) {
buffer <<= 8; buffer <<= 8
buffer |= data[index++] & 0xff; buffer |= data[index++] & 0xff
bitsLeft += 8; bitsLeft += 8
} else { } else {
const pad = SHIFT - bitsLeft; const pad = SHIFT - bitsLeft
buffer <<= pad; buffer <<= pad
bitsLeft += pad; bitsLeft += pad
} }
} }
bitsLeft -= SHIFT; bitsLeft -= SHIFT
result += ALPHABET[MASK & (buffer >> bitsLeft)]; result += ALPHABET[MASK & (buffer >> bitsLeft)]
} }
return result; return result
} }

View file

@ -68,7 +68,7 @@ if (app.get('onion_url')) {
app.use(stringReplace({ app.use(stringReplace({
PLACEHOLDER__PROXY_HOSTNAME: process.env.PROXY_HOSTNAME || process.env.DOMAIN || 'null' PLACEHOLDER__PROXY_HOSTNAME: process.env.PROXY_HOSTNAME || process.env.DOMAIN || 'null'
}, { }, {
contentTypeFilterRegexp: /application\/javascript/, contentTypeFilterRegexp: /application\/javascript/
})) }))
// Routes // Routes

953
yarn.lock

File diff suppressed because it is too large Load diff