Eselsohr is a self-hostable bookmark manager for storing web articles. Read them later or share access to your collection. It’s still in an early stage of development and not ready for production.
Pre-built binary releases are provided as CI artifacts.
To build the project manually, you’ll need GHC and the Cabal build tool. Download this repository and change your working directory into it. You can then install the executable with:
cabal install --install-method=copy --overwrite-policy=always
By default, the resulting binary gets stored in ~/.cabal/bin/eselsohr-exe
.
If you have Nix installed with Flakes support you can enter the development environment by running nix develop
.
Eselsohr is distributed as a single binary and does not have any other dependencies.
It can be configured by using env vars or by using a configuration file (eselsohr --config-file /path/to/file
).
The folder with the static resources is also required.
By default it looks for an .env
file and a static/
directory in the current working directory.
The following values can be set:
DATA_FOLDER
: File path where data is getting persisted. Defaults to XdgData.BASE_URL
: Base URL to generate HTML links. Defaults tohttp://localhost
.LOG_LEVEL
: Level for the built-in logger. Defaults to Error.PORT
: Port number on which the web server will listen. Defaults to6979
.LISTEN_ADDR
: Address where the web server will listen. Defaults to127.0.0.1
.HTTPS
: SendHSTS
HTTP header. Automatically enabled whenX-Forwarded-Proto
HTTP header is set tohttps
. Defaults toFalse
.DISABLE_HSTS
: Do not sendHSTS
HTTP header, whenHTTPS
is set. Defaults toFalse
.CERT_FILE
: File path to the TLS certificate file. Not set by default.KEY_FILE
: File path to the TLS key file. Not set by default.DEPLOYMENT_MODE
: The mode the application is running in. Can beProd
,Test
, orDev
. Defaults toProd
.PUBLIC_COLLECTION_CREATION
: Wether the creation of collection should be public. Defaults toFalse
.STATIC_FOLDER_PATH
: The path to the folder with static resources. Defaults tostatic/
.
Currently, all configuration parameters are optional.
Starting Eselsohr can be as simple as executing the Eselsohr binary in a directory along with the static resources.
If you don’t allow the public creation of collections, you can generate accesstokens for collections by running eselsohr-exe collection new
.
The dist
directory in this repository provides deployment relevant files, like an example rc
file for FreeBSD or a service file for systemd-based Linux distributions.
Alternatively, a Docker image is provided. You can build and run it like so:
sudo docker build -t eselsohr .
sudo docker run -p 6979:6979 -v eselsohr-data:/data eselsohr
The app is based on the Three Layer Cake architecture. It is similar to the Hexagonal, or Clean Architecture. The initial code was based on the three-layer project.
Eselsohr is used as a research playground for capability-based security in the context of web applications.
Common web applications use authentication with cookies or HTTP headers to enable identity-based authorization. This has certain disadvantages, some of them are listed in the description of the Waterken Server.
In Eselsohr authorization works with capabilities.
A capability is a shareable, unforgeable token that references a piece of data, including the associated set of access rights.
In our case, authorized requests work with access tokens, Base32 encoded binary data, which are transmitted either over HTTP query strings or in an HTML body.
An access token points to a Capability
, which points to a data structure called ObjectReference
.
They can also have some additional, optional properties like a petname, or an expiration date.
An object reference gives access to a collection or a single resource and has the associated permissions encoded within.
Object references are required for accessing the global state, like fetching an article with a specific ID. This is enforced by authorized actions: a data type which corresponds to user actions like creating a new article, or changing an article’s title. To obtain such an authorized action token one has to pass a object reference and in some cases the ID, on which the action will be performed, to functions which evaluate if the required permissions are set in the object reference. This forces us to do authorization checks and we can’t forget to do them.
The application tries to incorporate the Principle of Least Privilege wherever it can. Instead of using one single data storage for everything, each article collection is stored as a separate resource in the system. In theory, if Eselsohr would have a vulnerability like a SQL Injection, an attacker could only access their own data, because they do not have a reference to the other resources. Because access tokens are encoded binary data that point to an ID of a resource and the ID of a capability they work across multiple server instances, as long as the server has access to that resource.
Eselsohr is written in the programming language Haskell.
Although it’s never explicitly called one, Haskell is a great language to implement capability-based techniques, as functions are pure and data has to be explicitly passed as arguments to other functions and global state is rarely used.
Side-effects in Haskell are explicit and are normally done within the IO data type.
But IO alone means that any side-effect can happen.
Eselsohr uses type classes in a way that represent access to certain IO actions.
A function that wants to e.g. scrap a website, needs to have the MonadScraper
constraint in its signature.
All side-effects are therefore explicit and only effects that are wrapped this way can be used.