3box
Version:
Interact with user data
156 lines (140 loc) • 5.73 kB
JavaScript
const { fetchText, getMessageConsent } = require('./index')
const didJWT = require('did-jwt')
const { verifyMessage } = require('@ethersproject/wallet')
const config = require('../config.js')
const utils = require('./index')
const { Resolver } = require('did-resolver')
const get3IdResolver = require('3id-resolver').getResolver
const getMuportResolver = require('muport-did-resolver').getResolver
const getHttpsResolver = require('https-did-resolver').default
const PROFILE_SERVER_URL = config.profile_server_url
// Mocks ipfs obj for 3id resolve, to resolve through api, until ipfs instance is available
const ipfs = {
dag: {
get: async (cid) => {
const req = `${PROFILE_SERVER_URL}/did-doc?cid=${encodeURIComponent(cid)}`
return utils.fetchJson(req)
}
}
}
const resolver = new Resolver({
...get3IdResolver(ipfs, { pin: false }),
...getMuportResolver(),
...getHttpsResolver()
})
module.exports = {
/**
* Verifies that the gist contains the given 3ID and returns the users github username.
* Throws an error otherwise.
*
* @param {String} did The 3ID of the user (or array of equivalent dids)
* @param {Object} gistUrl URL of the proof
* @return {Object} Object containing username, and proof
*/
verifyGithub: async (did, gistUrl) => {
const dids = typeof did === 'string' ? [did] : did
if (!gistUrl || gistUrl.trim() === '') {
return null
}
let gistFileContent = await fetchText(gistUrl)
const includeDid = dids.reduce((acc, val) => (acc || gistFileContent.indexOf(val) !== -1), false)
if (!includeDid) {
throw new Error('Gist File provided does not contain the correct DID of the user')
}
const username = gistUrl.split('/')[3]
return {
username,
proof: gistUrl
}
},
/**
* Verifies that the tweet contains the given 3ID and returns the users twitter username.
* Throws an error otherwise.
*
* @param {String} did The 3ID of the user (or array of equivalent dids)
* @param {String} claim A did-JWT with claim
* @return {Object} Object containing username, proof, and the verifier
*/
verifyTwitter: async (did, claim) => {
const dids = typeof did === 'string' ? [did] : did
if (!claim) return null
const verified = await didJWT.verifyJWT(claim, { resolver })
if (!dids.includes(verified.payload.sub)) {
throw new Error('Verification not valid for given user')
}
const claimData = verified.payload.claim
if (!claimData.twitter_handle || !claimData.twitter_proof) {
throw new Error('The claim for your twitter is not correct')
}
return {
username: claimData.twitter_handle,
proof: claimData.twitter_proof,
verifiedBy: verified.payload.iss
}
},
/**
* Verifies that the code entered by the user is the same one that was sent via email.
* Throws an error otherwise.
*
* @param {String} did The 3ID of the user (or array of equivalent dids)
* @param {String} claim A did-JWT with claim
* @return {Object} Object containing username, proof, and the verifier
*/
verifyEmail: async (did, claim) => {
const dids = typeof did === 'string' ? [did] : did
if (!claim) return null
const verified = await didJWT.verifyJWT(claim, { resolver })
if (!dids.includes(verified.payload.sub)) {
throw new Error('Verification not valid for given user')
}
const claimData = verified.payload.claim
if (!claimData.email_address) {
throw new Error('The claim for your email address is not correct')
}
return {
email_address: claimData.email_address,
verifiedBy: verified.payload.iss
}
},
/**
* Verifies that the proof for a did is correct
*
* @param {String} claim A did-JWT with claim
* @return {String} The DID of the user
*/
verifyDID: async (claim) => {
const verified = await didJWT.verifyJWT(claim, { resolver })
const muport = verified.payload.muport
const res = {}
if (muport) {
const muportDID = (await didJWT.verifyJWT(muport, { resolver })).payload.iss
res.muport = muportDID
}
res.did = verified.payload.iss
return res
},
/**
* Verifies that the proof for an ethereum address is correct
*
* @param {Object} ethProof The claim generated by getLinkConsent
* @param {string} ethProof.consent_msg
* @param {string} ethProof.consent_signature
* @param {string} ethProof.linked_did
* @param {String} did The box' did
* @return {String} The ethereum address used to sign the message
*/
verifyEthereum: async (ethProof, did) => {
const dids = typeof did === 'string' ? [did] : did
// TODO - is this function needed? Can it be removed in
// favour of proofs that are in the rootstore?
const consentMsg = ethProof.version ? ethProof.message : ethProof['consent_msg']
const consentSig = ethProof.version ? ethProof.signature : ethProof['consent_signature']
// Make sure the message matches our expectation
const expected = getMessageConsent(did)
if (consentMsg !== expected) {
throw new Error(`Invalid consent message, got: "${consentMsg}", expected: "${expected}"`)
}
// Validate the signature
return verifyMessage(consentMsg, consentSig)
}
}