mirror of
https://github.com/lightninglabs/aperture.git
synced 2025-12-18 09:34:20 +01:00
aperture: Custom price per service
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
22
mint/mint.go
22
mint/mint.go
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user