mirror of
https://github.com/lightninglabs/aperture.git
synced 2025-12-18 17:44: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.
|
// Create the proxy and connect it to lnd.
|
||||||
genInvoiceReq := func() (*lnrpc.Invoice, error) {
|
genInvoiceReq := func(price int64) (*lnrpc.Invoice, error) {
|
||||||
return &lnrpc.Invoice{
|
return &lnrpc.Invoice{
|
||||||
Memo: "LSAT",
|
Memo: "LSAT",
|
||||||
Value: 1,
|
Value: price,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
servicesProxy, err := createProxy(cfg, genInvoiceReq, etcdClient)
|
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.
|
// NOTE: This is part of the Authenticator interface.
|
||||||
func (l *LsatAuthenticator) FreshChallengeHeader(r *http.Request,
|
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(
|
mac, paymentRequest, err := l.minter.MintLSAT(
|
||||||
context.Background(), service,
|
context.Background(), service,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ type Authenticator interface {
|
|||||||
|
|
||||||
// FreshChallengeHeader returns a header containing a challenge for the
|
// FreshChallengeHeader returns a header containing a challenge for the
|
||||||
// user to complete.
|
// 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
|
// 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
|
// FreshChallengeHeader returns a header containing a challenge for the user to
|
||||||
// complete.
|
// complete.
|
||||||
func (a MockAuthenticator) FreshChallengeHeader(r *http.Request,
|
func (a MockAuthenticator) FreshChallengeHeader(r *http.Request,
|
||||||
_ string) (http.Header, error) {
|
_ string, _ int64) (http.Header, error) {
|
||||||
|
|
||||||
header := r.Header
|
header := r.Header
|
||||||
header.Set(
|
header.Set(
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// InvoiceRequestGenerator is a function type that returns a new request for the
|
// InvoiceRequestGenerator is a function type that returns a new request for the
|
||||||
// lnrpc.AddInvoice call.
|
// 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
|
// LndChallenger is a challenger that uses an lnd backend to create new LSAT
|
||||||
// payment challenges.
|
// payment challenges.
|
||||||
@@ -57,10 +57,10 @@ func NewLndChallenger(cfg *authConfig, genInvoiceReq InvoiceRequestGenerator) (
|
|||||||
// request (invoice) and the corresponding payment hash.
|
// request (invoice) and the corresponding payment hash.
|
||||||
//
|
//
|
||||||
// NOTE: This is part of the Challenger interface.
|
// 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
|
// 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.
|
// so we can add it as a caveat to the macaroon.
|
||||||
invoice, err := l.genInvoiceReq()
|
invoice, err := l.genInvoiceReq(price)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error generating invoice request: %v", err)
|
log.Errorf("Error generating invoice request: %v", err)
|
||||||
return "", lntypes.ZeroHash, err
|
return "", lntypes.ZeroHash, err
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ type Service struct {
|
|||||||
|
|
||||||
// Tier is the tier of the LSAT-enabled service.
|
// Tier is the tier of the LSAT-enabled service.
|
||||||
Tier ServiceTier
|
Tier ServiceTier
|
||||||
|
|
||||||
|
// Price of service LSAT in satoshis.
|
||||||
|
Price int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServicesCaveat creates a new services caveat with the provided caveats.
|
// 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
|
// payment request. The payment hash is also returned as a convenience
|
||||||
// to avoid having to decode the payment request in order to retrieve
|
// to avoid having to decode the payment request in order to retrieve
|
||||||
// its payment hash.
|
// 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
|
// 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,
|
func (m *Mint) MintLSAT(ctx context.Context,
|
||||||
services ...lsat.Service) (*macaroon.Macaroon, string, error) {
|
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
|
// We'll start by retrieving a new challenge in the form of a Lightning
|
||||||
// payment request to present the requester of the LSAT with.
|
// 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 {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
@@ -143,6 +147,20 @@ func (m *Mint) MintLSAT(ctx context.Context,
|
|||||||
return mac, paymentRequest, nil
|
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
|
// createUniqueIdentifier creates a new LSAT identifier bound to a payment hash
|
||||||
// and a randomly generated ID.
|
// and a randomly generated ID.
|
||||||
func createUniqueIdentifier(paymentHash lntypes.Hash) ([]byte, error) {
|
func createUniqueIdentifier(paymentHash lntypes.Hash) ([]byte, error) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func newMockChallenger() *mockChallenger {
|
|||||||
return &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
|
return testPayReq, testHash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
case authLevel.IsOn():
|
case authLevel.IsOn():
|
||||||
if !p.authenticator.Accept(&r.Header, target.Name) {
|
if !p.authenticator.Accept(&r.Header, target.Name) {
|
||||||
prefixLog.Infof("Authentication failed. Sending 402.")
|
prefixLog.Infof("Authentication failed. Sending 402.")
|
||||||
p.handlePaymentRequired(w, r, target.Name)
|
p.handlePaymentRequired(w, r, target.Name, target.Price)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
p.handlePaymentRequired(w, r, target.Name)
|
p.handlePaymentRequired(w, r, target.Name, target.Price)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = target.freebieDb.TallyFreebie(r, remoteIP)
|
_, err = target.freebieDb.TallyFreebie(r, remoteIP)
|
||||||
@@ -281,11 +281,11 @@ func addCorsHeaders(header http.Header) {
|
|||||||
// handlePaymentRequired returns fresh challenge header fields and status code
|
// handlePaymentRequired returns fresh challenge header fields and status code
|
||||||
// to the client signaling that a payment is required to fulfil the request.
|
// to the client signaling that a payment is required to fulfil the request.
|
||||||
func (p *Proxy) handlePaymentRequired(w http.ResponseWriter, r *http.Request,
|
func (p *Proxy) handlePaymentRequired(w http.ResponseWriter, r *http.Request,
|
||||||
serviceName string) {
|
serviceName string, servicePrice int64) {
|
||||||
|
|
||||||
addCorsHeaders(r.Header)
|
addCorsHeaders(r.Header)
|
||||||
|
|
||||||
header, err := p.authenticator.FreshChallengeHeader(r, serviceName)
|
header, err := p.authenticator.FreshChallengeHeader(r, serviceName, servicePrice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error creating new challenge header: %v", err)
|
log.Errorf("Error creating new challenge header: %v", err)
|
||||||
sendDirectResponse(
|
sendDirectResponse(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/lightninglabs/aperture/auth"
|
"github.com/lightninglabs/aperture/auth"
|
||||||
"github.com/lightninglabs/aperture/freebie"
|
"github.com/lightninglabs/aperture/freebie"
|
||||||
)
|
)
|
||||||
@@ -19,6 +20,16 @@ var (
|
|||||||
filePrefixBase64 = filePrefix + "+base64"
|
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
|
// Service generically specifies configuration data for backend services to the
|
||||||
// Kirin proxy.
|
// Kirin proxy.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
@@ -69,6 +80,10 @@ type Service struct {
|
|||||||
// correspond to the caveat's condition.
|
// correspond to the caveat's condition.
|
||||||
Constraints map[string]string `long:"constraints" description:"The service constraints to enforce at the base tier"`
|
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
|
// AuthWhitelistPaths is an optional list of regular expressions that
|
||||||
// are matched against the path of the URL of a request. If the request
|
// 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
|
// URL matches any of those regular expressions, the call is treated as
|
||||||
@@ -154,6 +169,23 @@ func prepareServices(services []*Service) error {
|
|||||||
"whitelist: %v", err)
|
"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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ services:
|
|||||||
constraints:
|
constraints:
|
||||||
"valid_until": "2020-01-01"
|
"valid_until": "2020-01-01"
|
||||||
|
|
||||||
|
# The LSAT value in satoshis for the service.
|
||||||
|
price: 1
|
||||||
|
|
||||||
- name: "service2"
|
- name: "service2"
|
||||||
hostregexp: "service2.com:8083"
|
hostregexp: "service2.com:8083"
|
||||||
pathregexp: '^/.*$'
|
pathregexp: '^/.*$'
|
||||||
@@ -85,6 +88,7 @@ services:
|
|||||||
protocol: https
|
protocol: https
|
||||||
constraints:
|
constraints:
|
||||||
"valid_until": "2020-01-01"
|
"valid_until": "2020-01-01"
|
||||||
|
price: 1
|
||||||
|
|
||||||
# Settings for a Tor instance to allow requests over Tor as onion services.
|
# Settings for a Tor instance to allow requests over Tor as onion services.
|
||||||
# Configuring Tor is optional.
|
# Configuring Tor is optional.
|
||||||
|
|||||||
@@ -27,7 +27,11 @@ func newStaticServiceLimiter(proxyServices []*proxy.Service) *staticServiceLimit
|
|||||||
constraints := make(map[lsat.Service][]lsat.Caveat)
|
constraints := make(map[lsat.Service][]lsat.Caveat)
|
||||||
|
|
||||||
for _, proxyService := range proxyServices {
|
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(
|
capabilities[s] = lsat.NewCapabilitiesCaveat(
|
||||||
proxyService.Name, proxyService.Capabilities,
|
proxyService.Name, proxyService.Capabilities,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user