-
Notifications
You must be signed in to change notification settings - Fork 58
/
authenticator.go
124 lines (106 loc) · 3.38 KB
/
authenticator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package auth
import (
"context"
"encoding/base64"
"fmt"
"net/http"
"github.com/lightninglabs/aperture/l402"
"github.com/lightninglabs/aperture/mint"
"github.com/lightningnetwork/lnd/lnrpc"
)
// L402Authenticator is an authenticator that uses the L402 protocol to
// authenticate requests.
type L402Authenticator struct {
minter Minter
checker InvoiceChecker
}
// A compile time flag to ensure the L402Authenticator satisfies the
// Authenticator interface.
var _ Authenticator = (*L402Authenticator)(nil)
// NewL402Authenticator creates a new authenticator that authenticates requests
// based on L402 tokens.
func NewL402Authenticator(minter Minter,
checker InvoiceChecker) *L402Authenticator {
return &L402Authenticator{
minter: minter,
checker: checker,
}
}
// Accept returns whether or not the header successfully authenticates the user
// to a given backend service.
//
// NOTE: This is part of the Authenticator interface.
func (l *L402Authenticator) Accept(header *http.Header, serviceName string) bool {
// Try reading the macaroon and preimage from the HTTP header. This can
// be in different header fields depending on the implementation and/or
// protocol.
mac, preimage, err := l402.FromHeader(header)
if err != nil {
log.Debugf("Deny: %v", err)
return false
}
verificationParams := &mint.VerificationParams{
Macaroon: mac,
Preimage: preimage,
TargetService: serviceName,
}
err = l.minter.VerifyL402(context.Background(), verificationParams)
if err != nil {
log.Debugf("Deny: L402 validation failed: %v", err)
return false
}
// Make sure the backend has the invoice recorded as settled.
err = l.checker.VerifyInvoiceStatus(
preimage.Hash(), lnrpc.Invoice_SETTLED,
DefaultInvoiceLookupTimeout,
)
if err != nil {
log.Debugf("Deny: Invoice status mismatch: %v", err)
return false
}
return true
}
const (
// lsatAuthScheme is an outdated RFC 7235 auth-scheme used by aperture.
lsatAuthScheme = "LSAT"
// l402AuthScheme is the current RFC 7235 auth-scheme used by aperture.
l402AuthScheme = "L402"
)
// FreshChallengeHeader returns a header containing a challenge for the user to
// complete.
//
// NOTE: This is part of the Authenticator interface.
func (l *L402Authenticator) FreshChallengeHeader(r *http.Request,
serviceName string, servicePrice int64) (http.Header, error) {
service := l402.Service{
Name: serviceName,
Tier: l402.BaseTier,
Price: servicePrice,
}
mac, paymentRequest, err := l.minter.MintL402(
context.Background(), service,
)
if err != nil {
log.Errorf("Error minting L402: %v", err)
return nil, err
}
macBytes, err := mac.MarshalBinary()
if err != nil {
log.Errorf("Error serializing L402: %v", err)
}
header := http.Header{
"Content-Type": []string{"application/grpc"},
}
str := fmt.Sprintf("macaroon=\"%s\", invoice=\"%s\"",
base64.StdEncoding.EncodeToString(macBytes), paymentRequest)
// Old loop software (via ClientInterceptor code of aperture) looks
// for "LSAT" in the first instance of WWW-Authenticate header, so
// legacy header must go first not to break backward compatibility.
lsatValue := lsatAuthScheme + " " + str
l402Value := l402AuthScheme + " " + str
header.Set("WWW-Authenticate", lsatValue)
log.Debugf("Created new challenge header: [%s]", lsatValue)
header.Add("WWW-Authenticate", l402Value)
log.Debugf("Created new challenge header: [%s]", l402Value)
return header, nil
}