Skip to content
Permalink
Browse files
Delete vouch pages after four views. Update docs to explain why (stop…
…s a spammer getting a million-URLs-listed page vouched for). Use async to handle the initial SQL setup in a sane way.
  • Loading branch information
stuartlangridge committed Nov 30, 2014
1 parent fe5a928 commit ef0233d0573b6a151116f892983e0a81dc347077
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 37 deletions.
@@ -3,46 +3,34 @@ var express = require('express'),
bodyParser = require('body-parser'),
crypto = require('crypto'),
pg = require('pg'),
exphbs = require('express-handlebars');;
exphbs = require('express-handlebars'),
async = require('async');

// Create database contents table on startup
pg.connect(process.env.DATABASE_URL, function(err, client, done) {
client.query('create table if not exists vouches ' +
'(id serial primary key, source varchar, ' +
'created timestamp DEFAULT now() NOT NULL)', function(err, result) {
if (err) {
done(); throw new Error(err);
} else {
// Create function to delete rows after time N
// thank you http://www.the-art-of-web.com/sql/trigger-delete-old/
var fn = 'CREATE OR REPLACE FUNCTION delete_old_rows() RETURNS trigger ' +
pg.connect(process.env.DATABASE_URL, function(err, client, returnClientToPool) {
var sqls = [
'create table if not exists vouches ' +
'(id serial primary key, source varchar, ' +
'created timestamp DEFAULT now() NOT NULL)',
'DO $$ BEGIN BEGIN ALTER TABLE vouches ADD COLUMN views int default 0; ' +
"EXCEPTION WHEN duplicate_column THEN RAISE NOTICE 'Views column already present'; " +
'END; END; $$',
'CREATE OR REPLACE FUNCTION delete_old_rows() RETURNS trigger ' +
'LANGUAGE plpgsql AS $$ BEGIN ' +
"DELETE FROM vouches WHERE created < NOW() - INTERVAL '3 minutes';" +
'RETURN NEW; END; $$;';
client.query(fn, function(err, result) {
if (err) {
done(); throw new Error(err);
} else {
// and trigger that function to be called whenever we add new rows
var dtr = 'DROP TRIGGER IF EXISTS old_rows_gc ON vouches;';
client.query(dtr, function(err, result) {
if (err) {
done(); throw new Error(err);
} else {
var tr = 'CREATE TRIGGER old_rows_gc AFTER INSERT ON vouches ' +
'EXECUTE PROCEDURE delete_old_rows();';
client.query(tr, function(err, result) {
if (err) {
done(); throw new Error(err);
} else {
console.log("Database set up OK", result);
done();
}
});
}
});
}
});
'RETURN NEW; END; $$;',
'DROP TRIGGER IF EXISTS old_rows_gc ON vouches;',
'CREATE TRIGGER old_rows_gc AFTER INSERT ON vouches ' +
'EXECUTE PROCEDURE delete_old_rows();'
];
async.eachSeries(sqls, function(sql, cb) {
client.query(sql, function(err, result) {
returnClientToPool();
cb(err);
});
}, function(err) {
if (err) {
throw new Error(err);
}
});
});
@@ -74,7 +62,7 @@ app.get('/vouch/:id', function (req, res) {
console.log("Error connecting to db for vouch", req.params.id, err);
return res.status(500).render("error", {error: "There was a problem connecting to the database. Sorry."});
}
client.query('select source, created from vouches where id = $1::int', [req.params.id], function(err, result) {
client.query('select source, created, views from vouches where id = $1::int', [req.params.id], function(err, result) {
done(); // release client back to the pool
if (err) {
console.log("Error selecting for vouch", req.params.id, err);
@@ -84,6 +72,19 @@ app.get('/vouch/:id', function (req, res) {
return res.status(404).render("error", {error: "That vouch doesn't seem to exist."});
}
res.render("vouch", {source: result.rows[0].source});
if (result.rows[0].views > 3) {
client.query("delete from vouches where id = $1::int", [req.params.id], function(err, result) {
if (err) { console.log("Got error deleting exceeded views vouch", req.params.id); }
});
} else if (result.rows[0].views === null) {
client.query("update vouches set views = 1 where id = $1::int", [req.params.id], function(err, result) {
if (err) { console.log("Got error updating views count to 1 on vouch", req.params.id); }
});
} else {
client.query("update vouches set views = views + 1 where id = $1::int", [req.params.id], function(err, result) {
if (err) { console.log("Got error updating views count on vouch", req.params.id); }
});
}
});
});
});
@@ -17,6 +17,7 @@
},
"homepage": "https://github.com/stuartlangridge/hash-for-vouch",
"dependencies": {
"async": "^0.9.0",
"body-parser": "^1.9.3",
"express": "^4.10.4",
"express-handlebars": "^1.1.0",
@@ -36,6 +36,12 @@ while 1:
dynamically add links of pags they're webmentioning. This isn't peculiar to Hash for Vouch; it's a problem with Vouch
generally, but with Vouch generally the receiver can decide that they no longer trust site X to vouch for anyone. I don't want
people to treat Hash for Vouch in total as untrustworthy, so it goes to extra effort to avoid being used by spammers.</p>
<p>There are two reasons why vouch pages disappear. The first is that they are automatically deleted after 3 minutes even
if they're not used. The second is that they are automatically deleted once they've been viewed 4 times. Both of these are to
stop a spammer writing one page with a thousand URLs on, getting it vouched for, and then sending a thousand spammy webmentions
to those pages; once the first four have been sent and the recipient checks the vouch URL for validity, the vouch URL goes
away. None of this should be a problem for people using Hash for Vouch correctly; that is, plan to send a webmention, get a
vouch URL for it, send the webmention with the vouch URL attached, recipient checks the vouch URL, done.</p>

<h2>That's not really what a "nonce" is, in cryptographic jargon</h2>
<p>I know. But it's a convenient word, here.</p>

0 comments on commit ef0233d

Please sign in to comment.