Skip to content

Latest commit

 

History

History
465 lines (338 loc) · 20.9 KB

README.md

File metadata and controls

465 lines (338 loc) · 20.9 KB

code style: prettier Build Status Coverage Status

⚡️ Boltwall ⚡️

Bitcoin Lightning paywall and authentication using LSATs. Charge to access your API without requiring user accounts, API keys, credit cards, or storing any user data. All you need is a lightning node and a single line of code in your server. Built with LND, Nodejs, and Typescript.

All you need to put a paywall in front of a route on your server is this one line of code:

app.use(boltwall())

Supported Features

  • New LSAT protocol for authentication
  • Protect routes in your own API by using as a middleware
  • Custom attenuation via macaroons
  • Optional configurations available for time and IP restricted access
  • HODL Invoices
  • oAuth-like authorization for compatible 3rd party services (Coming soon)

System Requirements

  • Node > 11.10.0
  • npm > 6.9.0

Your project must also use Expressjs 4.x as well as the cors and body-parser middleware. Restify is also supported although not broadly tested.

Usage

To use as a middleware in an existing server, just install from npm into your project, and use before any routes that you want protected.

NOTE: Order matters with middleware. Any routes that appear after boltwall will require proper authentication in order to access. Boltwall can be used within routes to keep it isolated from other routes you want to remain free.

An example project can be seen in src/server.ts, which can be run with yarn start (after configuration).

A very simple server file, with no special configurations, could look like this:

const express = require('express')
const cors = require('cors')
const bodyParser = require('body-parser')

const { boltwall } = require('boltwall')

const app = express()

// middleware
app.use(cors())
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())

app.get('/', (_req, res) => {
  return res.json({
    message: 'Home route comes before boltwall and so is unprotected.',
  })
})

app.use(boltwall())

// this will require payment and proper lsat to access
app.get('/protected', (_req, res) =>
  res.json({
    message:
      'Protected route! This message will only be returned if an invoice has been paid',
  })
)

app.listen(5000, () => console.log('listening on port 5000!'))

Test the API

Running the example server

To run the test server, clone the repo, add appropriate configs to a local .env file, and from the directory run:

$ yarn install
$ yarn start

This runs the server located in /src/server.ts, which you can edit to test the middleware's behavior.

Once the server is running, you can test the API:

  1. GET http://localhost:5000/node to get connection information about your lightning node (no payment required).

  2. GET http://localhost:5000/protected?amount=[amount] will return a 402 error for payment required. An LSAT challenge will be available in the returned WWW-Authenticate header. Decode LSAT to get full invoice information. Amount in query string will be used in invoice generation unless below minAmount configured in boltwall. If no amount is set, then minAmount will be used.

  3. GET http://localhost:5000/protected with appropriate LSAT in Authorization header (including preimage) will return the response from the protected route

  4. POST http://localhost:5000/invoice with the following JSON body to get a new invoice (there is no relation to any lsat and so cannot be used for authentication): { "amount": 30 }

  5. GET http://localhost:5000/invoice?id=[payment hash] returns information for invoice with given payment hash including payment status and payreq.

Read more about the REST API in the documentation.

Required Environment Variables

Several environment variables are required when running boltwall. These serve the purpose of connecting to your lightning node and managing/signing macaroons for authentication/authorization.

Lightning Configs

Lightning configs (either lnd connection info or OpenNode API key depending on connection type) are required environment variables

If you are connecting to a lightning node you will need the following in your project's .env file or on the process.env object

Learn how to find these values for your node in this article by Will O'Beirne. You can also try this tool by lightning joule.

LND_TLS_CERT=[BASE64 or HEX encoded cert here]
LND_MACAROON=[BASE64 or HEX encoded cert here]
LND_SOCKET=[address of node here, e.g. localhost:10006]

Alternatively, if you are using OpenNode for managing payments, create an OpenNode account (currently testnet only), generate an API key and save it as:

OPEN_NODE_KEY=[API KEY HERE]

If you have both the lnd configs and OpenNode, lnd will take precedence.

A SESSION_SECRET can also be set but will be generated with a secure number of random bytes if none is provided.

SESSION_SECRET=[RANDOM STRING MINIMUM 32 BYTES IN LENGTH]

If you don't set this yourself, then the secret is not persisted between server restarts. This means that LSATs generated before the restart are no longer valid after restart since the signing key used to generate (and validate) macaroons will have changed.

What is an LSAT

LSATs are a new authentication scheme first introduced by Lightning Labs CTO Olaoluwa Osuntokun. Slides from the original presentation can be found here.

The idea is to create a standardized specification format for dealing with payment-based authentication, in contrast to the username/password (a.k.a. "Basic Authentication") and token based (most commonly used in OAuth constructions) authentication schemes in common use today. By combining a cryptographically verifiable proof of payment from a lightning invoice with its corresponding preimage and a macaroon, the idea is that a new, flexible, and private authorization protocol can be developed.

Checkout lsat-js for a separate utility library available for manipulating, interacting with, and validating LSATs. Available on npm: https://www.npmjs.com/package/lsat-js

Authentication Flow

There are three main stages to the authentication flow with LSATS:

  1. When a request is made to protected route, a 402 Payment Required response is sent back. This includes a WWW-Authenticate header with an LSAT. This is an encoded "challenge" that includes a macaroon (read more below to learn how these work) and an invoice value.

  2. The consumer of the API extracts and pays the invoice from the LSAT. After payment, a payment preimage should be revealed that acts as a cryptographically verifiable proof of payment.

  3. The consumer can now compose their LSAT using the preimage and the macaroon from the original challenge and put it in the Authorization http header like so: LSAT [macaroon]:[preimage]

Since the payment hash associated with the paid invoice could only have been generated using the preimage (also known as a "secret") and that preimage would be impossible to guess and so could only have been retrieved after paying the invoice, this serves as proof of payment and the server can verify that the request is authorized.

Custom Authorization w/ Macaroons

Boltwall allows for flexible authorization schemes. Effectively, this means that a server that is implementing Boltwall to protect content can dictate factors such as how long authorization is valid for based off of a payment or restrict access to only the originating IP.

As an example, the configuration in the example file src/server.ts, sets up authorization that is valid for 1 second for every satoshi paid in the invoice. So if a user pays a 30 satoshi invoice, then access is allowed for 30 seconds.

The config object should be passed to boltwall on initialization. e.g. app.use(boltwall(myConfig)), where myConfig provides the relevant properties. Currently, the config supports four properties: getCaveats(func), caveatSatisfiers (func), getInvoiceDescription, and minAmount.

More information on the configs can be found in the API Documentation.

Pre-built Configs

Boltwall exposes some default configs you can use in your own server. Simply import them from the module and then pass them into boltwall when useing it in your express server.

import { boltwall, TIME_CAVEAT_CONFIGS } from 'boltwall'

// ...  rest of your server code

app.use(boltwall(TIME_CAVEAT_CONFIGS))

// ... protected routes and any other server code

Since getCaveats and caveatSatisfiers can also accept an array of functions, you can also combine configs to add multiple caveats and their corresponding satisfiers.

Currently Boltwall provides the following configs:

TIME_CAVEAT_CONFIGS

This creates a restriction that any authorization is only valid for a number of seconds equal to the number of satoshis paid.

There is also support for a custom rate expressed in satoshis/second that can be added to the boltwall config object. The expiration time will be calculated according to this rate and the number of satoshis required for payment in the invoice.

For example, if the rate is .01 satoshis per second, and a 10,000 satoshi invoice requires payment to validate the LSAT, then a caveat will be attached that expires in 1,000,000 seconds (10000/.01) or approximately 11.5 days.

To get the above result, simply initialize boltwall like this:

app.use(boltwall({ ...TIME_CAVEAT_CONFIGS, rate: 0.01 }))

ORIGIN_CAVEAT_CONFIGS

This creates a restriction that any authorization is only valid from the IP that originally made the request for access.

ROUTE_CAVEAT_CONFIGS

This creates a restriction that any authorization is only valid for the route that originally was requested to access. There is an option to add a master route to your config to create an LSAT that works to access all routes. Another option is to allow sub-routes, meaning an LSAT to access "/protected" will also grant access to "/protected/images"

app.use(boltwall({ ...ROUTE_CAVEAT_CONFIGS, masterRoute: '/master', allowSubroutes: true }))

HODL Invoices

HODL invoices are a unique payment construction in lightning that allow a payee to simulate escrow-type situations or fidelity bonds in a lightning payment.

In a nutshell, a HODL invoice allows you to create a payment that does not automatically settle, but instead is "held" until someone (either the owner of the node or, more likely, some other party that is prepared to release the payment based on some conditions) reveals the preimage that the invoice is locked to. If this secret is never revealed, the held payment is eventually refunded back to the original payer. You can read more about how they are constructed and examples of potential use-cases here.

NOTE: Your lightning node MUST have the invoicesrpc flag enabled in order to support hodl invoices.

Boltwall includes basic support for creating and settling hodl invoices (cancelling must be done directly by the owner of the node and is not exposed via the Boltwall API at this time to avoid exposing potential double-spend-like risks).

Example Implementation and Access Flow

To enable a paywall that uses hodl invoices instead of normal invoices, simply initialize the middleware with hodl set to true in the config:

app.use((req, res, next) => {
  if (req.path === 'protected' && !req.body.paymentHash) {
    // Note: you'll probably want to save the secret somehow
    // otherwise the hodl invoice can't be redeemed
    const secret = crypto.randomBytes(32)
    const paymentHash = crypto
        .createHash('sha256')
        .update(secret)
        .digest()
    req.body.paymentHash = paymentHash
  }
  next()
})
app.use(boltwall({ hodl: true }))
// all routes after this will require payment to a hodl invoice
app.get('/protected', (req, res) => res.send('This route is protected'))
  1. The initial request for the route requires a paymentHash in the request body. This is how a hodl invoice is created. The payment hash can either be provided by the client or added in a middleware before boltwall is initialized (as in the example above).
  2. Boltwall will return an LSAT challenge in the WWW-Authenticate header as normal
  3. The client should pay the invoice.
  4. Since this is a hodl invoice, the client won't get a preimage until the invoice settles. How this is managed is up to the implementor and the architecture of the application. If the server is storing the secret, or retrieving it by some other means (such as with split payments), it can add it to the LSAT when it wants to invalidate future requests.
  5. Once the invoice settles and has a status of paid (instead of held) the LSAT is considered expired and future requests will be Unauthorized

Generation

The source of the pre-image and payment hash can be whatever you want. One common construction is for the pair to be tied to another invoice, which makes it so that the HODL invoice can't settle until another invoice settles and reveals its own preimage.

Authorization Flows

All authorization mechanisms (i.e. via macaroons) are preserved when using HODL invoices. The root macaroon is created and added to the LSAT when creating the invoice as normal. If time caveats are enabled, then this will timeout based on the amount paid. Custom configurations can be devised and passed in to boltwall upon initialization.

Making the Payment

When making the payment with your lightning client, it may look like the payment is stuck or is hanging. This is normal and is the result of a client having no way to know whether an invoice is normal or HODL so it is waiting for the invoice to settle. This won't happen for a HODL invoice until you settle it with the preimage.

A held Invoice is a Paid Invoice

What this means is that the paywall considers the invoice paid and "unlocks" the protected content, by providing the discharge macaroon even though your node technically may not have settled the payment yet and so doesn't have access to the funds.

Boltwall's default behavior for handling HODL LSAT requests is to settle an invoice when the secret is provided in the LSAT after the request has been authorized. This means that a request with the secret will be the last request for that LSAT since afterwards it will be paid and therefore "expired".

3rd Party Authentication

Usage

To enable, simply set oauth to true when initializing middleware:

import { boltwall } from 'boltwall'

// ...  rest of your server code

app.use(boltwall({ oauth: true }))

// ... protected routes and any other server code

This will add a requirement for requests to protected routes that an auth_uri be indicated in the request query. This indicates the 3rd party server that will process and confirm payments, ultimately signing a "challenge" to enable the holder of the corresponding LSAT access to the protected route.

Both the server processing requests for protected routes and the authorizing server signing the challenge must have oauth enabled.

NOTE: Boltwall's upgrade to support LSATs removed previous support for 3rd party caveats to enable OAuth-like constructions. A coordination step around a shared key was used to verify 3rd party caveats and corresponding discharge macaroons. Since Boltwall v5, a new protocol is used that no longer requires any coordination to support 3rd party authentication.

The use of macaroons for authorization allows for a lot of flexibility. Aside from the customization laid out in the section above covering the configurations, boltwall's API also enables authorization schemes with 3rd parties or as a 3rd party itself.

Think of it like running your own oAuth service.

In the same way that Google's oAuth allows a 3rd party service to sign you in to their platform by verifying your Google account, with Boltwall, your API can act like Google, where instead of verifying your account, the service verifies payment. An example of how this can be implemented is in the Prism Reader app. Prism Reader hosts documents provided by users. Authors of content can optionally require payment to view that content. Rather than Prism acting as a custodian for the funds and issuing payouts, an author can run a boltwall instance, give Prism the url of your API, and users will then only be able to read your content once your server has acknowledged payment!

Authentication flow

The below image should give an idea of the authentication flow between the boltwall api, a lightning node, a 3rd party app requiring authorization, and the client paying for access. boltwal architecture diagram

Documentation

Custom Configs & Caveats

Boltwall supports custom configurations that can be set on initialization. One important part of this is the ability to create custom macaroon caveats and the satisfiers your server will use to evaluate their validity. The TIME and ORIGIN configs described above are just two examples of these. Note that each caveat must have a corresponding satisfier in order for a macaroon to validate.

The properties that can be passed (none are required) to the boltwall config object are:

  • getCaveats: function or array of functions to generate caveats attached to LSAT macaroons
  • caveatSatisfiers- (required if getCaveats is set) Satisfier object or array of Satisfiers (see full documentation for more details on writing your own satisfiers) for evaluating caveats on a macaroon.
  • getInvoiceDescription - an optional function that returns a string to be used in the invoice description
  • minAmount - minimum amount to create invoices with if none is passed in request body
  • hodl- (optional, false by default) boolean, true to enable a hodl paywall.
  • oauth- (optional, false by default) boolean, true to enable 3rd party authentication.
  • rate - (optional, undefined by default) float, a rate to be used by other caveatGetters. For example, the TIME_CAVEAT_CONFIGS applies this as a satoshis/second value to calculate expiration.

More indepth documentation for these properties can be found in the docs

REST API

Check out the Swagger Docs for detailed API information. This details what to expect at the various routes provided for by Boltwall.

REST API

Full API Documentation

API documentation, with details on the code, API, and Typescript definitions can be found at the documentation website.