walnut
======

An opinionated, constrained, secure application framework with a hard shell - like iOS, but for a home server.

Security Features
-----------------

* JSON-only APIs
* JWT (not cookie*) authentication
* no server-rendered html
* disallows urlencoded forms, except for secured webhooks
* disallows cookies, except for protected static assets
* api.* subdomain for apis
* assets.* subdomain for protected assets
* *must* sit behind a trusted https proxy (such as [Goldilocks](https://git.daplie.com/Daplie/goldilocks.js)
* HTTPS-only (checks for X-Forwarded-For)
* AES, RSA, and ECDSA encryption and signing
* Safe against CSRF, XSS, and SQL injection
* Safe against Compression attacks

\*Cookies are used only for GETs and only where using a token would be less secure -
such as images which would otherwise require the token to be passed into the img src.
They are also scoped such that CSRF attacks are not possible.

Application Features
--------------------

* JSON-only expressjs APIs
* Capability-based permissions system for (oauth3-discoverable) packages such as
  * large file access (files@daplie.com)
  * database access (data@daplie.com)
  * scheduling (for background tasks, alerts, alarms, calendars, reminders, etc) (events@daplie.com)
  * payments (credit card) (payments@daplie.com)
  * email (email@daplie.com)
  * SMS (texting) (tel@daplie.com)
  * voice (calls and answering machine) (tel@daplie.com)
  * lamba-style functions (functions@daplie.com)
* Per-app, per-site, and per-user configurations
* Multi-Tentated Application Management
* Built-in OAuth2 & OAuth3 support

Install
-------

```bash
curl https://daplie.me/install-scripts | bash

daplie-install-walnut
```

You could also, of course, try installing from the repository directly
(especially if you have goldilocks or some similar already installed)

```bash
mkdir -p /srv/walnut/
git clone git@git.daplie.com:Daplie/walnut.js.git /srv/walnut/core
pushd /srv/walnut/core
  git checkout v1
popd
bash /srv/walnut/core/install-helper.sh
```

Initial Configuration
-------------

Once installed and started you can visit <https://localhost.daplie.me:3000>.

```
curl http://localhost.daplie.me:3000 -H 'X-Forwarded-Proto: https'
```

API
---

API docs are here https://git.daplie.com/Daplie/com.example.hello

Structure
=====

Currently being tested with Ubuntu, Raspbian, and Debian on Digital Ocean, Raspberry Pi, and Heroku.

```
/srv/walnut/
├── setup.sh (in-progress)
├── core
│   ├── bin
│   ├── boot
│   ├── holepunch
│   └── lib
├── etc
│   └── client-api-grants
├── node_modules
├── packages
│   ├── apis
│   ├── pages
│   └── services
└── var
    └── sites
```

* `core` contains all walnut code
* `node_modules` is a flat installation of all dependencies
* `certs` is a directory for Let's Encrypt (or custom) certificates
* `var` is a directory for database files and such
* `packages` contains 3 types of packages

Will install to
---------------

```
/srv/walnut/core/
/etc/walnut
/opt/walnut
/var/log/walnut
/etc/systemd/system/walnut.service
/etc/tmpfiles.d/walnut.conf
```

Implementation details
----------------

Initialization
--------------

needs to know its primary domain

```
POST https://api.<domain.tld>/api/walnut@daplie.com/init

{ "domain": "<domain.tld>" }
```

The following domains are required to point to WALNUT server

```
<domain.tld>
www.<domain.tld>

api.<domain.tld>
assets.<domain.tld>

cloud.<domain.tld>
api.cloud.<domain.tld>
```

Example `/etc/goldilocks/goldilocks.yml`:
```yml
tls:
  email: domains@example.com
  servernames:
    - example.com
    - www.example.com
    - api.example.com
    - assets.example.com
    - cloud.example.com
    - api.cloud.example.com

http:
  trust_proxy: true
  modules:
    - name: proxy
      domains:
        - '*'
      address: '127.0.0.1:3000'
```

Resetting the Initialization
----------------------------

Once you run the app the initialization files will appear in these locations

```
/srv/walnut/var/walnut+config@daplie.com.sqlite3
/srv/walnut/config/<domain.tld>/config.json
```

Deleting those files will rese

Accessing static apps
---------------------

Static apps are stored in `packages/pages`

```
# App ID as files with a list of packages they should load
# note that '#' is used in place of '/' because files and folders may not contain '/' in their names
/srv/walnut/packages/sites/<domain.tld#path>          # https://domain.tld/path
/srv/walnut/packages/sites/<domain.tld>               # https://domain.tld and https://domain.tld/foo match

# packages are directories with reverse dns name      # For the sake of debugging these packages can be accessed directly, without a site by
/srv/walnut/packages/pages/<tld.domain.package>       # matches apps.<domain.tld>/<package-name> and <domain.tld>/apps/<package-name>
```

Accessing REST APIs
-------------------

```
# Apps are granted access to use a package by listing it in the grants file by the name of the app url (domain.tld)
/srv/walnut/packages/client-api-grants/<domain.tld>   # matches api.<domain.tld>/api/ and contains a list of allowed REST APIs
                                                      # the REST apis themselves are submatched as api.<domain.tld>/api/<tld.domain.package>

# packages are directories with reverse dns name, a package.json, and an index.js
/srv/walnut/packages/rest/<tld.domain.package>
```

Example tree with contents:

Here `com.example.hello` is a package with a REST API and a static page
and `foobar.me` is a WALNUT-configured domain (smithfam.net, etc).


The packages:

```
/srv/walnut/packages/
├── api
├── pages
│   └── com.example.hello
│       └── index.html
│             '''
│             <html>
│               <head><title>com.example.hello</title></head>
│               <body>
│                 <h1>com.example.hello</h1>
│               </body>
│             </html>
│             '''
│
├── rest
│   └── com.example.hello
│      ├── package.json
│      └── index.js
│           '''
│           'use strict';
│
│           module.exports.create = function (conf, deps, app) {
│
│             app.use('/', function (req, res) {
│               console.log('[com.example.hello] req.url', req.url);
│               res.send({ message: 'hello' });
│             });
│
│             return deps.Promise.resolve();
│           };
│
│           '''
│
└── services
```

The permissions:

```
/srv/walnut/packages/
├── client-api-grants
│   └── cloud.foobar.me
│         '''
│         com.example.hello     # refers to /srv/walnut/packages/rest/com.example.hello
│         '''
│
└── sites
    └── daplie.me
          '''
          com.example.hello     # refers to /srv/walnut/packages/pages/com.example.hello
          '''
```

API
---

```
req.apiUrlPrefix => https://api.example.com/api/tld.domain.pkg
```

TODO
----

* HTTPS Key Pinning
* Heroku (pending completion of PostgreSQL support)
* [GunDB](https://gundb.io) Support
* OpenID support
