File handling

The Haplo Platform provides a file store, which is integrated into the other parts of the API.

Files are represented by the File interface, and the O.file() constructor can retrieve a File from the store from any other representation.

Files are addressed by their SHA256 cryptographic digest and file size. As the chance of a digest collision is minimal, digest based addressing allows unambiguous references to files throughout the API along with built in assurances of file integrity.

Files can be generated and transformed using the file transform pipeline API.

API integration

Object store

append() a FileIdentifier value to a StoreObjectMutable.

While the underlying file store does not have a concept of versions of a file, the user interface uses the trackingId property of the FileIdentifier to identify a ‘file version track’ through the previous versions of a StoreObject. The version and logMessage properties are then used to display version numbers and change logs to the user.

Forms

Use file and file repeating section elements on a form to allow users to upload files as part of form completion.

When forms are rendered, a thumbnail and filename will be displayed, linked to a download URL which includes an authentication token.

Request handler

Declare an as:"file" argument on a request handler, and receive a BinaryData argument on your request handler function. But it’s probably more convenient and a better user experience to use the forms system.

You can set E.response.body to a File object to send the file as the response. Important! Be careful you make the appropriate security checks when overriding the platform file security handling.

Database

Declare a "file" type field in your database tables.

Security

It’s important that 1) users can only download files they’re permitted to see, and 2) permission to download a file can be revoked. Therefore, knowledge of a digest is not sufficient to be able to download a file.

The platform will allow a user to download a file if either:

  • an object can be found which includes a FileIdentifier for the file, and the user is permitted to read it, or
  • the download URL includes an authentication signature tied to the user’s current session.

All that’s left is to make sure that the user can’t add a FileIdentifier for a file they want to an object they can read, or insert a digest into some other database which gives them permission.

Your code should be carefully written so it does not accidentally give access to a file. Unlike the object store, it’s not possible for the platform to automatically enforce permissions as file handling can be used in so many ways.

The platform provides a per-file secret which can only be generated by the server. If you are sending a digest to a client and then storing it again when submitted by the user, then you should also send the secret and use the checkSecret() function to check the value which is returned. (The other way a user can prove they have access to a file is to upload it, so the BinaryData object, used for file uploads, doesn’t need a secret.)

The object editor and the forms system handle these secrets for you, so use them whenever practical.