Tinfoil

Hi there

At VALA 2016 I presented a paper about a conceptual client-side encrypted integrated library management system.

The paper is on the VALA website.

A demo site showing some basic functionality is now available at tinfoil.hugh.li. Here's a rundown of the basic functionality:

Sign Up

User authentication uses the built-in Meteor accounts-password package, which hashes passwords using bcrypt. It should also send you an email if you put in a valid email address and click ‘forgot my password’, but since I haven’t set up email on the server, it won’t actually send the email. Sorry.

To create an account:
* Click on Sign In and then Create account.

Search collection

Uses the easy:search package. Start typing a title, author or subject, and matching items will start appearing below. You can’t actually do anything with these results as this is just a demo, but in a real system results would link to more detailed records.

Staff tasks

Here you can add items to the collection by ‘cataloguing’ them. Obviously this would be a proper, MARC and Dublin Core compliant cataloguing module in a real life system.

Add a title, one or more authors separated by commas, and one or more subjects separated by commas, and click add record. Items appear instantly in the system.

Member Record

This section shows the current user’s current and previous loans, and allows the user to initiate a ‘Finish Borrowing’ action.

Note that only the user can see their loan history and current loans in plain text. The information is stored on the server in encrypted form, and decrypted in the client.

Updating loan records

In the demo system, when you click on 'Member Record' in the menu, a process is triggered to check the user's current loans. This is because the system can't identify which Borrowers belong to the user unless the user is logged in.1 The system checks whether the borrowers listed against the user still exist, and updates the 'current loans' and 'total loans count' accordingly.

In a production system we would probably do it a bit differently: perhaps an always-logged-in app on the user's smartphone, or a check when they log in to the loans kiosk at a library branch.

Finish Borrowing

The Finish Borrowing function is a bit clunky, but demonstrates how the You might also like function actually works. Pressing this button finalises a batch of loans that are then considered to be linked to a single loan session. I would expect in a production system this function would be built in to a kiosk session, whereby logging in to the kiosk triggers a session and logging or timing out would trigger a session to be closed (the action simulated here by the Finish Borrowing button). Under the hood, it looks like this:

var issuedToday = [];

...

// Update item records with info about items issued together
'youMightAlsoLike' : function(){  
  // Create a new array. We use this later to update 
  // the loanedWith array without interfering with 
  // issuedToday (above).
  var freshLoans = [];

  // add all the new items to loanedWith, but
  // only if they're not already there.
  function clean(d){
    // pull out all the IDs in 'loanedWith' and create
    // a variable with all duplicates removed.
    lw = Books.findOne(d).loanedWith;            
    var newArray = issuedToday.concat(lw);            
    // dedupe using underscore's _.uniq
    var deduped = _.uniq(newArray);

    // function to stop the item's own id from being added
    // also excludes null from the array
    function notSelf(id) {
      return (id !== d && id !== null && id !== "null");
    };

    // filter the array using notSelf
    var newLoanedWith = deduped.filter(notSelf);
    // update the item record with the new loanedWith entry
    Books.update({_id: d}, {$set: {loanedWith: newLoanedWith}});
  };

  function clearToday (e){
    // clear out loanedToday and freshLoans now that 
    // we've cycled through them.
    issuedToday.length = 0;
  };

  // THIS IS WHERE THE ACTION IS
  // call clean function for each item issued in this batch
  issuedToday.forEach(function(d){
    clean(d)
  });
  // clear out the issuedToday array
  clearToday();
}

What we're doing here is updating the loanedWith entry for each item that is issued. We created an issuedToday array, then when the 'Finish Borrowing' button is pressed we iterate through it, pushing everything in issuedToday (except the item itself) into the item's loanedWith array, then deduplicating. This becomes important when we consider You might also like... below.

Issue Items

Here you can issue items to the logged-in user by entering an ID number and pressing Enter or clicking Borrow it! In this test system it’s easiest to simply browse the collection and copy one of the ID numbers.

One of the principals of Tinfoil is that the library user can see their loans but the system itself (and therefore library staff and anyone else) can't. But we need to be able to track loan status of items through time, and eventually tie loans back to a user somehow. We do this by creating a 'borrower' whenever an item is borrowed, and tying that borrower to a user in a way that is clear to the user but not to anyone else. There's a lot of code involved in doing this, but essentially what happens is:

  1. The system looks up the user's Mylar 'Principal' - an identity used to encrypt data in the client.
  2. The item is issued to a new 'borrower' with a unique ID, with a due date.
  3. The borrower ID is added to an (encrypted) array of all the borrower IDs associated with this user.

Return Items

Works in more or less exactly the same way as Issue Items, but returns items instead.

The trick here is that the work of removing the link between the borrower and the user is not done at the point of return, but rather at the point the user logs in (or updates their view of current loans). This is because if we're returning items on a client machine where the user isn't logged in - a typical scenario - all the information that links the user to the borrow is encrypted and can't be read by the system.2

Browse Items

Browse the entire collection. Items are listed from oldest to most recent issue date, then by ID number. You can see title, author/s and subject/s, as well as the ID number and whether it is on loan or available.

You might also like...

This section would ordinarily be built in to the item record, but I’ve kept it separate in this demo system to separate out the different functionalities.

The system makes a recommendation of you might also like X, based on all the items that have been borrowed with the particular item in question and each other. It’s not a particularly sophisticated recommendation engine, but gives a flavour of the possibilities available even with a system that has ‘zero knowledge’ of who is borrowing what. To find the 'best match', we use the following code:

  // create empty object
  var tally = {};
  // get the item        
  var book = Books.findOne(x);
  // get loanWith list
  var lw = book.loanedWith;
  if (lw) {
    // for each item in the loanedWith listing
    // add it to the tally, or iterate its value
    // by one if it already exists
    lw.forEach(function(x){
      tally[x] = (tally[x] || 0) + 1;
    });
    // get the highest value in the tally object
    // (using Underscore, which comes with Meteor)
    var topLoans = _.max(tally);
    // invert the object so we can find the key by the 
    // value (i.e. which item has the highest value)
    var inverted = _.invert(tally);
    // get the id of the item, which is now the 
    // value rather than the key
    var mostPopular = inverted[topLoans];
    // get the actual items        
    var popBook = Books.findOne(mostPopular);        
    // return the title
    if (popBook) {
      return popBook.title;
    }            
  }

We take each item in the loanedWith array (i.e. the ID of every item that has been borrowed with the item we're looking at), and then we pull out all of the items in their loanedWith arrays. We push everything into a new object, storing how many times each item ID appears. Because sorting an object by key values is difficult, we then use underscore to flip the object around so the values become keys. We can then simply sort by key to find the highest number. The value of this key will be the ID of the item that appears most often with the items that have been borrowed with the particular item we're looking at.

You can see all the code on GitHub.

  1. See Issue Items below.

  2. See Updating loan records, above.