forked from Mirrors/keyoxide-web
Add API data sanitization
This commit is contained in:
parent
d89d970a15
commit
5b5ccbb590
4 changed files with 229 additions and 20 deletions
196
api/v0/index.js
196
api/v0/index.js
|
@ -29,17 +29,149 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
|
|||
*/
|
||||
const router = require('express').Router()
|
||||
const { check, validationResult } = require('express-validator')
|
||||
const Ajv = require("ajv")
|
||||
const ajv = new Ajv({coerceTypes: true})
|
||||
const kx = require('../../server')
|
||||
|
||||
const apiProfileSchema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
keyData: {
|
||||
type: "object",
|
||||
properties: {
|
||||
fingerprint: {
|
||||
type: "string"
|
||||
},
|
||||
users: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
userData: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
name: { type: "string" },
|
||||
email: { type: "string" },
|
||||
comment: { type: "string" },
|
||||
isPrimary: { type: "boolean" },
|
||||
isRevoked: { type: "boolean" },
|
||||
}
|
||||
},
|
||||
claims: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
claimVersion: { type: "integer" },
|
||||
uri: { type: "string" },
|
||||
fingerprint: { type: "string" },
|
||||
status: { type: "string" },
|
||||
matches: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
serviceProvider: {
|
||||
type: "object",
|
||||
properties: {
|
||||
type: { type: "string" },
|
||||
name: { type: "string" },
|
||||
}
|
||||
},
|
||||
match: {
|
||||
type: "object",
|
||||
properties: {
|
||||
regularExpression: { type: "object" },
|
||||
isAmbiguous: { type: "boolean" },
|
||||
}
|
||||
},
|
||||
profile: {
|
||||
type: "object",
|
||||
properties: {
|
||||
display: { type: "string" },
|
||||
uri: { type: "string" },
|
||||
qr: { type: "string" },
|
||||
}
|
||||
},
|
||||
proof: {
|
||||
type: "object",
|
||||
properties: {
|
||||
uri: { type: "string" },
|
||||
request: {
|
||||
type: "object",
|
||||
properties: {
|
||||
fetcher: { type: "string" },
|
||||
access: { type: "string" },
|
||||
format: { type: "string" },
|
||||
data: { type: "object" },
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
claim: {
|
||||
type: "object",
|
||||
properties: {
|
||||
format: { type: "string" },
|
||||
relation: { type: "string" },
|
||||
path: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
verification: {
|
||||
type: "object"
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
primaryUserIndex: {
|
||||
type: "integer"
|
||||
},
|
||||
key: {
|
||||
type: "object",
|
||||
properties: {
|
||||
data: { type: "object" },
|
||||
fetchMethod: { type: "string" },
|
||||
uri: { type: "string" },
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
extra: {
|
||||
type: "object",
|
||||
properties: {
|
||||
avatarURL: { type: "string" },
|
||||
}
|
||||
},
|
||||
errors: {
|
||||
type: "array"
|
||||
},
|
||||
},
|
||||
required: ["keyData", "extra", "errors"],
|
||||
additionalProperties: false
|
||||
}
|
||||
|
||||
const apiProfileValidate = ajv.compile(apiProfileSchema)
|
||||
|
||||
const doVerification = async (data) => {
|
||||
let promises = []
|
||||
let results = []
|
||||
|
||||
for (let iUser = 0; iUser < data.keyData.users.length; iUser++) {
|
||||
const user = data.keyData.users[iUser];
|
||||
const user = data.keyData.users[iUser]
|
||||
|
||||
for (let iClaim = 0; iClaim < user.claims.length; iClaim++) {
|
||||
const claim = user.claims[iClaim];
|
||||
const claim = user.claims[iClaim]
|
||||
|
||||
promises.push(
|
||||
new Promise(async (resolve, reject) => {
|
||||
|
@ -59,6 +191,34 @@ const doVerification = async (data) => {
|
|||
return data
|
||||
}
|
||||
|
||||
const sanitize = (data) => {
|
||||
let results = []
|
||||
|
||||
const dataClone = JSON.parse(JSON.stringify(data))
|
||||
|
||||
for (let iUser = 0; iUser < dataClone.keyData.users.length; iUser++) {
|
||||
const user = dataClone.keyData.users[iUser]
|
||||
|
||||
for (let iClaim = 0; iClaim < user.claims.length; iClaim++) {
|
||||
const claim = user.claims[iClaim]
|
||||
|
||||
// TODO Fix upstream
|
||||
if (!claim.verification) {
|
||||
claim.verification = {}
|
||||
}
|
||||
|
||||
data.keyData.users[iUser].claims[iClaim] = claim
|
||||
}
|
||||
}
|
||||
|
||||
const valid = apiProfileValidate(data)
|
||||
if (!valid) {
|
||||
throw new Error(`Profile data sanitization error`)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
router.get('/profile/fetch',
|
||||
check('query').exists(),
|
||||
check('protocol').optional().toLowerCase().isIn(["hkp", "wkd"]),
|
||||
|
@ -100,7 +260,21 @@ router.get('/profile/fetch',
|
|||
data = await doVerification(data)
|
||||
}
|
||||
|
||||
res.send(data)
|
||||
try {
|
||||
// Sanitize JSON
|
||||
data = sanitize(data);
|
||||
} catch (error) {
|
||||
data.keyData = {}
|
||||
data.extra = {}
|
||||
data.errors = [error.message]
|
||||
}
|
||||
|
||||
let statusCode = 200
|
||||
if (data.errors.length > 0) {
|
||||
statusCode = 500
|
||||
}
|
||||
|
||||
res.status(500).send(data)
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -116,7 +290,21 @@ router.get('/profile/verify',
|
|||
// Do verification
|
||||
data = await doVerification(req.query.data)
|
||||
|
||||
res.send(data)
|
||||
try {
|
||||
// Sanitize JSON
|
||||
data = sanitize(data);
|
||||
} catch (error) {
|
||||
data.keyData = {}
|
||||
data.extra = {}
|
||||
data.errors = [error.message]
|
||||
}
|
||||
|
||||
let statusCode = 200
|
||||
if (data.errors.length > 0) {
|
||||
statusCode = 500
|
||||
}
|
||||
|
||||
res.status(500).send(data)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"description": "A modern, secure and privacy-friendly platform to establish your decentralized online identity",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"ajv": "^8.6.3",
|
||||
"bent": "^7.3.12",
|
||||
"body-parser": "^1.19.0",
|
||||
"dialog-polyfill": "^0.5.6",
|
||||
|
|
|
@ -37,7 +37,7 @@ const generateWKDProfile = async (id) => {
|
|||
let keyData = await doip.keys.process(key.publicKey)
|
||||
keyData.key.fetchMethod = 'wkd'
|
||||
keyData.key.uri = key.fetchURL
|
||||
keyData.key.data = null
|
||||
keyData.key.data = {}
|
||||
keyData = processKeyData(keyData)
|
||||
|
||||
return {
|
||||
|
@ -49,9 +49,9 @@ const generateWKDProfile = async (id) => {
|
|||
})
|
||||
.catch(err => {
|
||||
return {
|
||||
key: null,
|
||||
keyData: null,
|
||||
extra: null,
|
||||
key: {},
|
||||
keyData: {},
|
||||
extra: {},
|
||||
errors: [err.message]
|
||||
}
|
||||
})
|
||||
|
@ -63,7 +63,7 @@ const generateHKPProfile = async (id, keyserverDomain) => {
|
|||
let keyData = await doip.keys.process(key.publicKey)
|
||||
keyData.key.fetchMethod = 'hkp'
|
||||
keyData.key.uri = key.fetchURL
|
||||
keyData.key.data = null
|
||||
keyData.key.data = {}
|
||||
keyData = processKeyData(keyData)
|
||||
|
||||
return {
|
||||
|
@ -75,9 +75,9 @@ const generateHKPProfile = async (id, keyserverDomain) => {
|
|||
})
|
||||
.catch(err => {
|
||||
return {
|
||||
key: null,
|
||||
keyData: null,
|
||||
extra: null,
|
||||
key: {},
|
||||
keyData: {},
|
||||
extra: {},
|
||||
errors: [err.message]
|
||||
}
|
||||
})
|
||||
|
@ -88,7 +88,7 @@ const generateSignatureProfile = async (signature) => {
|
|||
.then(async key => {
|
||||
let keyData = key.keyData
|
||||
delete key.keyData
|
||||
keyData.key.data = null
|
||||
keyData.key.data = {}
|
||||
keyData = processKeyData(keyData)
|
||||
|
||||
return {
|
||||
|
@ -100,9 +100,9 @@ const generateSignatureProfile = async (signature) => {
|
|||
})
|
||||
.catch(err => {
|
||||
return {
|
||||
key: null,
|
||||
keyData: null,
|
||||
extra: null,
|
||||
key: {},
|
||||
keyData: {},
|
||||
extra: {},
|
||||
errors: [err.message]
|
||||
}
|
||||
})
|
||||
|
@ -114,7 +114,7 @@ const generateKeybaseProfile = async (username, fingerprint) => {
|
|||
let keyData = await doip.keys.process(key.publicKey)
|
||||
keyData.key.fetchMethod = 'hkp'
|
||||
keyData.key.uri = key.fetchURL
|
||||
keyData.key.data = null
|
||||
keyData.key.data = {}
|
||||
keyData = processKeyData(keyData)
|
||||
|
||||
return {
|
||||
|
@ -126,9 +126,9 @@ const generateKeybaseProfile = async (username, fingerprint) => {
|
|||
})
|
||||
.catch(err => {
|
||||
return {
|
||||
key: null,
|
||||
keyData: null,
|
||||
extra: null,
|
||||
key: {},
|
||||
keyData: {},
|
||||
extra: {},
|
||||
errors: [err.message]
|
||||
}
|
||||
})
|
||||
|
|
20
yarn.lock
20
yarn.lock
|
@ -645,6 +645,16 @@ ajv@^6.12.3:
|
|||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@^8.6.3:
|
||||
version "8.6.3"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.3.tgz#11a66527761dc3e9a3845ea775d2d3c0414e8764"
|
||||
integrity sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.1"
|
||||
json-schema-traverse "^1.0.0"
|
||||
require-from-string "^2.0.2"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ansi-align@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz"
|
||||
|
@ -2666,6 +2676,11 @@ json-schema-traverse@^0.4.1:
|
|||
resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz"
|
||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||
|
||||
json-schema-traverse@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
|
||||
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
|
||||
|
||||
json-schema@0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz"
|
||||
|
@ -3706,6 +3721,11 @@ require-directory@^2.1.1:
|
|||
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"
|
||||
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
|
||||
|
||||
require-from-string@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
|
||||
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
|
||||
|
||||
require-main-filename@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz"
|
||||
|
|
Loading…
Reference in a new issue