110 lines
2.4 KiB
JavaScript
110 lines
2.4 KiB
JavaScript
const express = require('express');
|
|
const { Level } = require('level');
|
|
const { recoverPersonalSignature } = require('@metamask/eth-sig-util');
|
|
const objectHash = require('object-hash');
|
|
|
|
require('dotenv').config();
|
|
|
|
const app = express();
|
|
const port = process.env.PORT || 3000;
|
|
const dataDir = process.env.DATA_DIR || 'data';
|
|
|
|
const db = new Level(`${dataDir}/forum`, { valueEncoding: 'json' });
|
|
|
|
const verifySignature = ({
|
|
author, content, signature, embeddedData,
|
|
}) => {
|
|
let contentToVerify = content;
|
|
if (embeddedData && Object.entries(embeddedData).length) {
|
|
contentToVerify += `\n\n${JSON.stringify(embeddedData, null, 2)}`;
|
|
}
|
|
try {
|
|
const account = recoverPersonalSignature({ data: contentToVerify, signature });
|
|
if (account !== author) {
|
|
console.log('error: author does not match signature');
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
console.log('error: failed to recover signature:', e.message);
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
app.use(express.json());
|
|
|
|
app.post('/write', async (req, res) => {
|
|
const {
|
|
body: {
|
|
author, content, signature, embeddedData,
|
|
},
|
|
} = req;
|
|
// Check author signature
|
|
if (!verifySignature({
|
|
author, content, signature, embeddedData,
|
|
})) {
|
|
res.status(403).end();
|
|
return;
|
|
}
|
|
|
|
// Compute content hash
|
|
const data = {
|
|
author, content, signature, embeddedData,
|
|
};
|
|
const hash = objectHash(data);
|
|
console.log('write', hash);
|
|
console.log(data);
|
|
|
|
// Store content
|
|
db.put(hash, data);
|
|
|
|
// Return hash
|
|
res.send(hash);
|
|
});
|
|
|
|
app.get('/read/:hash', async (req, res) => {
|
|
const { hash } = req.params;
|
|
console.log('read', hash);
|
|
|
|
// Fetch content
|
|
let data;
|
|
try {
|
|
data = await db.get(req.params.hash);
|
|
} catch (e) {
|
|
console.log('read error:', e.message, hash);
|
|
res.status(e.status).end();
|
|
return;
|
|
}
|
|
|
|
data.embeddedData = data.embeddedData || undefined;
|
|
|
|
console.log(data);
|
|
|
|
// Verify hash
|
|
const derivedHash = objectHash(data);
|
|
if (derivedHash !== hash) {
|
|
console.log('error: hash mismatch');
|
|
res.status(500).end();
|
|
return;
|
|
}
|
|
|
|
// Verify signature
|
|
if (!verifySignature(data)) {
|
|
console.log('error: signature verificaition failed');
|
|
res.status(500).end();
|
|
return;
|
|
}
|
|
|
|
// Return content
|
|
res.json(data);
|
|
});
|
|
|
|
app.get('*', (req, res) => {
|
|
console.log(`404 req.path: ${req.path}`);
|
|
res.status(404).json({ errorCode: 404 });
|
|
});
|
|
|
|
app.listen(port, () => {
|
|
console.log(`Listening on port ${port}`);
|
|
});
|