mirror of
https://github.com/lightninglabs/aperture.git
synced 2026-01-31 07:04:26 +01:00
proxy: add demo and README with the use cases
This commit is contained in:
57
README.md
Normal file
57
README.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Lightning Service Authentication Token (LSAT) proxy
|
||||
|
||||
Kirin is a HTTP reverse proxy that supports proxying requests for gRPC (HTTP/2)
|
||||
and REST (HTTP/1 and HTTP/2) backends.
|
||||
|
||||
## Installation
|
||||
|
||||
See [INSTALL.md](install.md).
|
||||
|
||||
## Demo
|
||||
|
||||
There is a demo installation available at
|
||||
[test-staging.swap.lightning.today:8081](https://test-staging.swap.lightning.today:8081).
|
||||
|
||||
### Use Case 1: Web GUI
|
||||
|
||||
If you visit the demo installation in the browser, you see a simple web GUI.
|
||||
There you can request the current BOS scores for testnet. Notice that you can
|
||||
only request the scores three times per IP addres. After the free requests have
|
||||
been used up, you receive an LSAT token/macaroon and are challenged to pay an
|
||||
invoice to authorize it.
|
||||
|
||||
You have two options to pay for the invoice:
|
||||
|
||||
1. If you have Joule installed in your browser and connected to a testnet node,
|
||||
you can click the "Pay invoice with Joule" button to pay the invoice. After
|
||||
successful payment the page should automatically refresh.
|
||||
1. In case you want to pay the invoice manually, copy the payment request to
|
||||
your wallet of choice that has the feature to reveal the preimage after a
|
||||
successful payment. Copy the payment preimage in hex format, then click the
|
||||
button "Paste preimage of manual payment" and paste it in the dialog box.
|
||||
|
||||
### Use Case 2: cURL
|
||||
|
||||
First, let's request the BOS scores until we hit the freebie limit:
|
||||
|
||||
`curl -k -v https://test-staging.swap.lightning.today:8081/availability/v1/btc.json`
|
||||
|
||||
At some point, we will get an answer 402 with an authorization header:
|
||||
|
||||
```
|
||||
www-authenticate: LSAT macaroon='...' invoice='lntb10n1...'
|
||||
```
|
||||
|
||||
We will need both these values, the `macaroon` and the `invoice` so copy them
|
||||
to a text file somewhere (without the single quotes!).
|
||||
Let's pay the invoice now, choose any LN wallet that displays the preimage after
|
||||
a successful payment. Copy the hex encoded preimage to the text file too once
|
||||
you get it from the wallet.
|
||||
|
||||
Finally, you can issue the authenticated request with the following command:
|
||||
|
||||
```
|
||||
curl -k -v \
|
||||
--header "Authorization: LSAT <macaroon>:<preimage>" \
|
||||
https://test-staging.swap.lightning.today:8081/availability/v1/btc.json
|
||||
```
|
||||
@@ -173,6 +173,12 @@ func (p *Proxy) director(req *http.Request) {
|
||||
// Don't forward the authorization header since the
|
||||
// services won't know what it is.
|
||||
req.Header.Del("Authorization")
|
||||
|
||||
// Now overwrite header fields of the client request
|
||||
// with the fields from the configuration file.
|
||||
for name, value := range target.Headers {
|
||||
req.Header.Add(name, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/lightninglabs/kirin/auth"
|
||||
"github.com/lightninglabs/kirin/freebie"
|
||||
)
|
||||
|
||||
var (
|
||||
filePrefix = "!file"
|
||||
filePrefixHex = filePrefix + "+hex"
|
||||
filePrefixBase64 = filePrefix + "+base64"
|
||||
)
|
||||
|
||||
// Service generically specifies configuration data for backend services to the
|
||||
// Kirin proxy.
|
||||
type Service struct {
|
||||
@@ -32,6 +44,17 @@ type Service struct {
|
||||
// of the URL of a request to find out if this service should be used.
|
||||
PathRegexp string `long:"pathregexp" description:"Regular expression to match the path of the URL against"`
|
||||
|
||||
// Headers is a map of strings that defines header name and values that
|
||||
// should always be passed to the backend service, overwriting any
|
||||
// headers with the same name that might have been set by the client
|
||||
// request.
|
||||
// If the value of a header field starts with the prefix "!file+hex:",
|
||||
// the rest of the value is treated as a path to a file and the content
|
||||
// of that file is sent to the backend with each call (hex encoded).
|
||||
// If the value starts with the prefix "!file+base64:", the content of
|
||||
// the file is sent encoded as base64.
|
||||
Headers map[string]string `long:"headers" description:"Header fields to always pass to the service"`
|
||||
|
||||
freebieDb freebie.DB
|
||||
}
|
||||
|
||||
@@ -45,6 +68,43 @@ func prepareServices(services []*Service) error {
|
||||
service.Auth.FreebieCount(),
|
||||
)
|
||||
}
|
||||
|
||||
// Replace placeholders/directives in the header fields with the
|
||||
// actual desired values.
|
||||
for key, value := range service.Headers {
|
||||
if !strings.HasPrefix(value, filePrefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Split(value, ":")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid header config, " +
|
||||
"must be '!file+hex:path'")
|
||||
}
|
||||
prefix, fileName := parts[0], parts[1]
|
||||
bytes, err := ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// There are two supported formats to encode the file
|
||||
// content in: hex and base64.
|
||||
switch {
|
||||
case prefix == filePrefixHex:
|
||||
newValue := hex.EncodeToString(bytes)
|
||||
service.Headers[key] = newValue
|
||||
|
||||
case prefix == filePrefixBase64:
|
||||
newValue := base64.StdEncoding.EncodeToString(
|
||||
bytes,
|
||||
)
|
||||
service.Headers[key] = newValue
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported file prefix "+
|
||||
"format %s", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,125 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>LSAT proxy demo page</title>
|
||||
<style>
|
||||
.row:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.col {
|
||||
width: 45%;
|
||||
float: left;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.max-height-scroll {
|
||||
max-height: 500px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap; /* Since CSS 2.1 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>LSAT auth server</h1>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1>LND node info</h1>
|
||||
<pre id="getinfo-lnd"></pre>
|
||||
<button id="reload-lnd">Reload</button>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h1>Bos Scores</h1>
|
||||
<pre id="bos-scores" class="max-height-scroll"></pre>
|
||||
<button id="reload-bos">Reload</button>
|
||||
<button id="pay">Pay invoice with Joule</button>
|
||||
<button id="add-preimage">Paste preimage of manual payment</button>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
|
||||
<script src="https://unpkg.com/webln@0.2.1/dist/webln.min.js"
|
||||
integrity="sha384-Enk2tnv6U0yPoFk7RasscZ5oQIG2fzVYaG4ledkAf7MdEXP9fMazV74tAgEwvxm7"
|
||||
crossorigin="anonymous"></script>
|
||||
<script>
|
||||
|
||||
let authorization = "";
|
||||
let lastMacaroon = null;
|
||||
let lastInvoice = null;
|
||||
|
||||
function parseInvoice(invoice) {
|
||||
const rex = /LSAT macaroon='(.*?)' invoice='(.*?)'/i;
|
||||
parts = invoice.match(rex);
|
||||
lastMacaroon = parts[1];
|
||||
lastInvoice = parts[2];
|
||||
}
|
||||
|
||||
function loadJSON(url, elem) {
|
||||
elem.text("");
|
||||
$.ajax(url, {
|
||||
cache: false,
|
||||
dataType: 'json',
|
||||
crossDomain: true,
|
||||
headers: {
|
||||
'Authorization': authorization,
|
||||
},
|
||||
success: (data, status) => {
|
||||
elem.text(JSON.stringify(data, null, 2));
|
||||
},
|
||||
statusCode: {
|
||||
402: (xhr, status, err) => {
|
||||
var invoice = xhr.getResponseHeader('www-authenticate');
|
||||
parseInvoice(invoice);
|
||||
elem.text("payment required: " + invoice);
|
||||
}
|
||||
},
|
||||
error: (xhr, status, err) => {
|
||||
console.log("error: " + err + ", headers: " + xhr.getAllResponseHeaders())
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function payInvoice() {
|
||||
if (window.WebLN) {
|
||||
WebLN.requestProvider()
|
||||
.then((provider) => {
|
||||
provider.sendPayment(lastInvoice)
|
||||
.then((response) => {
|
||||
authorization = "LSAT " + lastMacaroon + ":" + response.preimage;
|
||||
$('#reload-bos').click();
|
||||
$('#reload-lnd').click();
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
alert(err);
|
||||
});
|
||||
} else {
|
||||
alert("Joule not installed or failed to load WebLN library.");
|
||||
}
|
||||
}
|
||||
|
||||
function addPreimage() {
|
||||
let preimage = prompt("Enter hex encoded preimage");
|
||||
authorization = "LSAT " + lastMacaroon + ":" + preimage;
|
||||
$('#reload-bos').click();
|
||||
$('#reload-lnd').click();
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
const host = document.location.host;
|
||||
|
||||
$('#reload-lnd').on('click', () => loadJSON('//alice.' + host + '/v1/getinfo', $('#getinfo-lnd'))).click();
|
||||
$('#reload-bos').on('click', () => loadJSON('//' + host + '/availability/v1/btc.json', $('#bos-scores'))).click();
|
||||
$('#pay').on('click', () => payInvoice());
|
||||
$('#add-preimage').on('click', () => addPreimage());
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user