aperture: Custom price per service

This commit is contained in:
Elle Mouton
2020-05-10 15:49:48 +02:00
parent 57a5605990
commit 162571ac45
12 changed files with 82 additions and 17 deletions

View File

@@ -96,10 +96,10 @@ func start() error {
}
// Create the proxy and connect it to lnd.
genInvoiceReq := func() (*lnrpc.Invoice, error) {
genInvoiceReq := func(price int64) (*lnrpc.Invoice, error) {
return &lnrpc.Invoice{
Memo: "LSAT",
Value: 1,
Value: price,
}, nil
}
servicesProxy, err := createProxy(cfg, genInvoiceReq, etcdClient)

View File

@@ -59,9 +59,13 @@ func (l *LsatAuthenticator) Accept(header *http.Header, serviceName string) bool
//
// NOTE: This is part of the Authenticator interface.
func (l *LsatAuthenticator) FreshChallengeHeader(r *http.Request,
serviceName string) (http.Header, error) {
serviceName string, servicePrice int64) (http.Header, error) {
service := lsat.Service{Name: serviceName, Tier: lsat.BaseTier}
service := lsat.Service{
Name: serviceName,
Tier: lsat.BaseTier,
Price: servicePrice,
}
mac, paymentRequest, err := l.minter.MintLSAT(
context.Background(), service,
)

View File

@@ -18,7 +18,7 @@ type Authenticator interface {
// FreshChallengeHeader returns a header containing a challenge for the
// user to complete.
FreshChallengeHeader(*http.Request, string) (http.Header, error)
FreshChallengeHeader(*http.Request, string, int64) (http.Header, error)
}
// Minter is an entity that is able to mint and verify LSATs for a set of

View File

@@ -32,7 +32,7 @@ func (a MockAuthenticator) Accept(header *http.Header, _ string) bool {
// FreshChallengeHeader returns a header containing a challenge for the user to
// complete.
func (a MockAuthenticator) FreshChallengeHeader(r *http.Request,
_ string) (http.Header, error) {
_ string, _ int64) (http.Header, error) {
header := r.Header
header.Set(

View File

@@ -12,7 +12,7 @@ import (
// InvoiceRequestGenerator is a function type that returns a new request for the
// lnrpc.AddInvoice call.
type InvoiceRequestGenerator func() (*lnrpc.Invoice, error)
type InvoiceRequestGenerator func(price int64) (*lnrpc.Invoice, error)
// LndChallenger is a challenger that uses an lnd backend to create new LSAT
// payment challenges.
@@ -57,10 +57,10 @@ func NewLndChallenger(cfg *authConfig, genInvoiceReq InvoiceRequestGenerator) (
// request (invoice) and the corresponding payment hash.
//
// NOTE: This is part of the Challenger interface.
func (l *LndChallenger) NewChallenge() (string, lntypes.Hash, error) {
func (l *LndChallenger) NewChallenge(price int64) (string, lntypes.Hash, error) {
// Obtain a new invoice from lnd first. We need to know the payment hash
// so we can add it as a caveat to the macaroon.
invoice, err := l.genInvoiceReq()
invoice, err := l.genInvoiceReq(price)
if err != nil {
log.Errorf("Error generating invoice request: %v", err)
return "", lntypes.ZeroHash, err

View File

@@ -46,6 +46,9 @@ type Service struct {
// Tier is the tier of the LSAT-enabled service.
Tier ServiceTier
// Price of service LSAT in satoshis.
Price int64
}
// NewServicesCaveat creates a new services caveat with the provided caveats.

View File

@@ -27,7 +27,7 @@ type Challenger interface {
// payment request. The payment hash is also returned as a convenience
// to avoid having to decode the payment request in order to retrieve
// its payment hash.
NewChallenge() (string, lntypes.Hash, error)
NewChallenge(price int64) (string, lntypes.Hash, error)
}
// SecretStore is the store responsible for storing LSAT secrets. These secrets
@@ -93,9 +93,13 @@ func New(cfg *Config) *Mint {
func (m *Mint) MintLSAT(ctx context.Context,
services ...lsat.Service) (*macaroon.Macaroon, string, error) {
// Let the LSAT value as the price of the most expensive of the
// services.
price := maximumPrice(services)
// We'll start by retrieving a new challenge in the form of a Lightning
// payment request to present the requester of the LSAT with.
paymentRequest, paymentHash, err := m.cfg.Challenger.NewChallenge()
paymentRequest, paymentHash, err := m.cfg.Challenger.NewChallenge(price)
if err != nil {
return nil, "", err
}
@@ -143,6 +147,20 @@ func (m *Mint) MintLSAT(ctx context.Context,
return mac, paymentRequest, nil
}
// maximumPrice determines the necessary price to use for a collection
// of services.
func maximumPrice(services []lsat.Service) int64 {
var max int64
for _, service := range services {
if service.Price > max {
max = service.Price
}
}
return max
}
// createUniqueIdentifier creates a new LSAT identifier bound to a payment hash
// and a randomly generated ID.
func createUniqueIdentifier(paymentHash lntypes.Hash) ([]byte, error) {

View File

@@ -26,7 +26,7 @@ func newMockChallenger() *mockChallenger {
return &mockChallenger{}
}
func (d *mockChallenger) NewChallenge() (string, lntypes.Hash, error) {
func (d *mockChallenger) NewChallenge(price int64) (string, lntypes.Hash, error) {
return testPayReq, testHash, nil
}

View File

@@ -97,7 +97,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case authLevel.IsOn():
if !p.authenticator.Accept(&r.Header, target.Name) {
prefixLog.Infof("Authentication failed. Sending 402.")
p.handlePaymentRequired(w, r, target.Name)
p.handlePaymentRequired(w, r, target.Name, target.Price)
return
}
@@ -116,7 +116,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
if !ok {
p.handlePaymentRequired(w, r, target.Name)
p.handlePaymentRequired(w, r, target.Name, target.Price)
return
}
_, err = target.freebieDb.TallyFreebie(r, remoteIP)
@@ -281,11 +281,11 @@ func addCorsHeaders(header http.Header) {
// handlePaymentRequired returns fresh challenge header fields and status code
// to the client signaling that a payment is required to fulfil the request.
func (p *Proxy) handlePaymentRequired(w http.ResponseWriter, r *http.Request,
serviceName string) {
serviceName string, servicePrice int64) {
addCorsHeaders(r.Header)
header, err := p.authenticator.FreshChallengeHeader(r, serviceName)
header, err := p.authenticator.FreshChallengeHeader(r, serviceName, servicePrice)
if err != nil {
log.Errorf("Error creating new challenge header: %v", err)
sendDirectResponse(

View File

@@ -9,6 +9,7 @@ import (
"regexp"
"strings"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/aperture/auth"
"github.com/lightninglabs/aperture/freebie"
)
@@ -19,6 +20,16 @@ var (
filePrefixBase64 = filePrefix + "+base64"
)
const (
// defaultServicePrice is price in satoshis to be used as the default
// service price.
defaultServicePrice = 1
// maxServicePrice is the maximum price in satoshis that can be used
// to create an invoice through lnd.
maxServicePrice = btcutil.SatoshiPerBitcoin * 100000
)
// Service generically specifies configuration data for backend services to the
// Kirin proxy.
type Service struct {
@@ -69,6 +80,10 @@ type Service struct {
// correspond to the caveat's condition.
Constraints map[string]string `long:"constraints" description:"The service constraints to enforce at the base tier"`
// Price is the custom LSAT value in satoshis to be used for the
// service's endpoint.
Price int64 `long:"price" description:"Static LSAT value in satoshis to be used for this service"`
// AuthWhitelistPaths is an optional list of regular expressions that
// are matched against the path of the URL of a request. If the request
// URL matches any of those regular expressions, the call is treated as
@@ -154,6 +169,23 @@ func prepareServices(services []*Service) error {
"whitelist: %v", err)
}
}
// Check that the price for the service is not negative and not
// more than the maximum amount allowed by lnd. If no price, or
// a price of zero satoshis, is set the then default price of 1
// satoshi is to be used.
switch {
case service.Price == 0:
log.Debugf("Using default LSAT price of %v satoshis for "+
"service %s.", defaultServicePrice, service.Name)
service.Price = defaultServicePrice
case service.Price < 0:
return fmt.Errorf("negative price set for "+
"service %s", service.Name)
case service.Price > maxServicePrice:
return fmt.Errorf("maximum price exceeded for "+
"service %s", service.Name)
}
}
return nil
}

View File

@@ -78,6 +78,9 @@ services:
constraints:
"valid_until": "2020-01-01"
# The LSAT value in satoshis for the service.
price: 1
- name: "service2"
hostregexp: "service2.com:8083"
pathregexp: '^/.*$'
@@ -85,6 +88,7 @@ services:
protocol: https
constraints:
"valid_until": "2020-01-01"
price: 1
# Settings for a Tor instance to allow requests over Tor as onion services.
# Configuring Tor is optional.

View File

@@ -27,7 +27,11 @@ func newStaticServiceLimiter(proxyServices []*proxy.Service) *staticServiceLimit
constraints := make(map[lsat.Service][]lsat.Caveat)
for _, proxyService := range proxyServices {
s := lsat.Service{Name: proxyService.Name, Tier: lsat.BaseTier}
s := lsat.Service{
Name: proxyService.Name,
Tier: lsat.BaseTier,
Price: proxyService.Price,
}
capabilities[s] = lsat.NewCapabilitiesCaveat(
proxyService.Name, proxyService.Capabilities,
)