So this is an interesting little proof of concept exercise.

Bit of background, I’ve recently worked on a small project which required the creation of a link generator. The purpose of this was:

  • To provide time limited access to a web based resource
  • To obfuscate the link to that resource so that it couldn’t be accessed after a set period of time
  • To allow the creation and removal of this access

The middle point proved to be the easiest as the resource could be embedded in a HTML page via a Javascript API, but limiting the access in as easy a way possible was the tough bit.

I ended up building a “self destructing link generator” in PHP, tied to a MySQL back end, which would allow me to create a hash based on the current datetime and the unique data to be associated with the resource’s API call, stash that in a database alongside the datetime and create a page that would:

  • Accept the hashed ID in a querystring
  • Check if the data is still valid (within 24 hours of link creation)
  • Retrieve the data if it is and display the resource
  • Destroy the record if not and feed back to the user

The logic behind it was straightforward and I coded it in PHP because it’s what I’m most famliar with, however at the start of the project I felt this would be an ideal candidate for a lightweight Node.js app, ideally one that stashes the link data in a NoSQL database or even in no database at all.

Fast forward a few weeks and I’ve ticked that off my to do list and built a prototype for what such a system might look like. You can grab the code from GitHub here.

It’s a fairly simple app which runs through Express with Pug as the render engine and has a few Node processes that do the heavy lifting. Firstly, we take the output from a form, generate the current datetime, use all that data to create our hash and then stash it as JSON into a text file, named after the hash.

site.post('/addrecord', function(req, res){
    var date = moment().format('YYYY-MM-DD:hh:mm:ss');
    var data = req.body;
    data['date'] = date;
    var filename = data.name+data.email+data.date;
    filename = crypto.createHash('md5').update(filename).digest('hex');
    fs.writeFile(path.join(__dirname, 'links/'+filename+'.txt'), JSON.stringify(data), function(err) {
        if (err) console.log(err);
        console.log("File written!");
    });
    url = req.protocol+'://'+req.headers.host+'/link?linkid='+filename;
    res.render('addrecord', {url: url} );
});

Then we have a process which grabs the hash passed in a querystring, retrieves and displays the data in the file if it’s still valid and destroys the file if it’s not.

site.get('/link', function(req, res){
    var linkid = req.query.linkid;
    console.log(linkid);
    var filename = (path.join(__dirname, 'links/'+linkid+'.txt'));
    fs.readFile(filename, "utf-8", function(err, data) {
        if(err) {
            console.log(err);
            res.render('link', { error: "This file doesn't exist!" });
        } else {
            data = JSON.parse(data);
            if(checkDate(data.date,"24")){
                console.log(data);
                res.render('link', { data: data } );
            } else {
                fs.unlink(filename, function(err) {
                    if(err){
                        console.log(err)
                    } else {
                        console.log("Date invalid - file deleted.");
                    }
                });
                res.render('link', { error: "The date was invalid, so I deleted the file!" });
            }
        }
    });
});

Why flat files? Initially I thought about using MongoDB to stash the data but I wanted to keep this process as simple as possible. There’s absolutely no reason that this couldn’t be extended to use Mongo, MySQL or the database of your choice.

And there we have it; it’s a simple enough proof of concept but I think it’s got some interesting real world applications. There’s a few things that could be done just to beef it up a little, such as:

  • Rehashing the data on load to do an integrity check and ensure the file hadn’t been meddled with
  • Some kind to config to allow granular set up with what data’s being captured and what’s being used in the hash
  • Some kind of refactoring on the code to make it easily reusable (I’ll likely do this if I ever come to use it in a full fat project)

Feel free to fork and clone the code and let me know if you find it useful in any way!