Skip to content

Fix error handling with stateless init #289

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ If you’d prefer to compile from source code please follow [these instructions]
Lightning Terminal is backwards compatible with `lnd` back to version v0.13.3-beta.

| LiT | LND |
| ---------------- | ------------ |
|------------------| ------------ |
| **v0.6.1-alpha** | v0.13.3-beta |
| **v0.6.0-alpha** | v0.13.3-beta |
| **v0.5.2-alpha** | v0.12.0-beta |
| **v0.5.1-alpha** | v0.12.0-beta |
Expand All @@ -86,8 +87,8 @@ Lightning Terminal is backwards compatible with `lnd` back to version v0.13.3-be
## Daemon Versions packaged with LiT

| LiT | LND | Loop | Faraday | Pool |
| ---------------- | ------------ | ----------- | ------------ |---------------|
| **v0.6.0-alpha** | v0.14.1-beta | v0.15.1-beta | v0.2.7-alpha | v0.5.2-alpha |
|------------------| ------------ | ----------- | ------------ |---------------|
| **v0.6.1-alpha** | v0.14.1-beta | v0.15.1-beta | v0.2.7-alpha | v0.5.2-alpha |
| **v0.5.3-alpha** | v0.13.3-beta | v0.14.1-beta | v0.2.6-alpha | v0.5.0-alpha |
| **v0.5.2-alpha** | v0.13.3-beta | v0.14.1-beta | v0.2.6-alpha | v0.5.0-alpha |
| **v0.5.1-alpha** | v0.13.0-beta | v0.14.1-beta | v0.2.6-alpha | v0.5.0-alpha |
Expand Down
9 changes: 2 additions & 7 deletions app/src/components/connect/ConnectPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import { Redirect } from 'react-router';
import { observer } from 'mobx-react-lite';
import styled from '@emotion/styled';
import nodeConnectSvg from 'assets/images/lightning-node-connect.svg';
Expand All @@ -12,7 +11,7 @@ import SessionList from './SessionList';

const Styled = {
Wrapper: styled.section`
padding: 80px 0 0 80px;
padding-top: 80px;
`,
DisplayLarge: styled.div`
font-family: ${props => props.theme.fonts.open.semiBold};
Expand All @@ -33,11 +32,7 @@ const Styled = {

const ConnectPage: React.FC = () => {
const { l } = usePrefixedTranslation('cmps.connect.ConnectPage');
const { appView, sessionStore } = useStore();

if (!appView.showLightningConnect) {
return <Redirect to="/loop" />;
}
const { sessionStore } = useStore();

const { Wrapper, DisplayLarge, Description, Divider } = Styled;
return !sessionStore.hasMultiple ? (
Expand Down
8 changes: 1 addition & 7 deletions app/src/components/layout/NavMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,7 @@ const NavMenu: React.FC = () => {
<NavItem page="history" onClick={appView.goToHistory} />
<NavItem page="pool" badge={l('common.preview')} onClick={appView.goToPool} />
<NavItem page="settings" onClick={appView.goToSettings} />
{appView.showLightningConnect && (
<NavItem
page="connect"
badge={l('common.beta')}
onClick={appView.goToConnect}
/>
)}
<NavItem page="connect" badge={l('common.beta')} onClick={appView.goToConnect} />
</Nav>
</>
);
Expand Down
7 changes: 0 additions & 7 deletions app/src/store/views/appView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,6 @@ export default class AppView {
}
}

/** determines if the Lightning Node Connect UI should be visible */
get showLightningConnect() {
const devOverride = !!localStorage.getItem('i-want-lnc');
/** the unix timestamp (ms) when Lightning Node Connect should become visible */
return devOverride || Date.now() > 1638288000000; // Nov 30 2021 11:00am EST
}

/** Change to the Auth page */
gotoAuth() {
this.goTo(`${PUBLIC_URL}/`);
Expand Down
85 changes: 69 additions & 16 deletions rpc_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@ const (
HeaderMacaroon = "Macaroon"
)

// proxyErr is an error type that adds more context to an error occurring in the
// proxy.
type proxyErr struct {
wrapped error
proxyContext string
}

// Error returns the error message as a string, including the proxy's context.
func (e *proxyErr) Error() string {
return fmt.Sprintf("proxy error with context %s: %v", e.proxyContext,
e.wrapped)
}

// Unwrap returns the wrapped error.
func (e *proxyErr) Unwrap() error {
return e.wrapped
}

// newRpcProxy creates a new RPC proxy that can take any native gRPC, grpc-web
// or REST request and delegate (and convert if necessary) it to the correct
// component.
Expand Down Expand Up @@ -282,7 +300,7 @@ func (p *rpcProxy) director(ctx context.Context,
authHeaders := md.Get("authorization")
if len(authHeaders) == 1 {
macBytes, err := p.basicAuthToMacaroon(
authHeaders[0], requestURI,
authHeaders[0], requestURI, nil,
)
if err != nil {
return outCtx, nil, err
Expand Down Expand Up @@ -332,10 +350,17 @@ func (p *rpcProxy) UnaryServerInterceptor(
// have proper macaroon support implemented in the UI. We allow
// gRPC web requests to have it and "convert" the auth into a
// proper macaroon now.
newCtx, err := p.convertBasicAuth(ctx, info.FullMethod)
newCtx, err := p.convertBasicAuth(ctx, info.FullMethod, nil)
if err != nil {
return nil, fmt.Errorf("error upgrading basic auth: %v",
err)
// Make sure we handle the case where the super macaroon
// is still empty on startup.
if pErr, ok := err.(*proxyErr); ok &&
pErr.proxyContext == "supermacaroon" {

return nil, fmt.Errorf("super macaroon error: "+
"%v", pErr)
}
return nil, err
}

// With the basic auth converted to a macaroon if necessary,
Expand Down Expand Up @@ -369,9 +394,19 @@ func (p *rpcProxy) StreamServerInterceptor(
// have proper macaroon support implemented in the UI. We allow
// gRPC web requests to have it and "convert" the auth into a
// proper macaroon now.
ctx, err := p.convertBasicAuth(ss.Context(), info.FullMethod)
ctx, err := p.convertBasicAuth(
ss.Context(), info.FullMethod, nil,
)
if err != nil {
return fmt.Errorf("error upgrading basic auth: %v", err)
// Make sure we handle the case where the super macaroon
// is still empty on startup.
if pErr, ok := err.(*proxyErr); ok &&
pErr.proxyContext == "supermacaroon" {

return fmt.Errorf("super macaroon error: "+
"%v", pErr)
}
return err
}

// With the basic auth converted to a macaroon if necessary,
Expand All @@ -390,21 +425,23 @@ func (p *rpcProxy) StreamServerInterceptor(
// convertBasicAuth tries to convert the HTTP authorization header into a
// macaroon based authentication header.
func (p *rpcProxy) convertBasicAuth(ctx context.Context,
requestURI string) (context.Context, error) {
requestURI string, ctxErr error) (context.Context, error) {

md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return ctx, nil
return ctx, ctxErr
}

authHeaders := md.Get("authorization")
if len(authHeaders) == 0 {
// No basic auth provided, we don't add a macaroon and let the
// gRPC security interceptor reject the request.
return ctx, nil
return ctx, ctxErr
}

macBytes, err := p.basicAuthToMacaroon(authHeaders[0], requestURI)
macBytes, err := p.basicAuthToMacaroon(
authHeaders[0], requestURI, ctxErr,
)
if err != nil || len(macBytes) == 0 {
return ctx, err
}
Expand All @@ -416,8 +453,8 @@ func (p *rpcProxy) convertBasicAuth(ctx context.Context,
// basicAuthToMacaroon checks that the incoming request context has the expected
// and valid basic authentication header then attaches the correct macaroon to
// the context so it can be forwarded to the actual gRPC server.
func (p *rpcProxy) basicAuthToMacaroon(basicAuth, requestURI string) ([]byte,
error) {
func (p *rpcProxy) basicAuthToMacaroon(basicAuth, requestURI string,
ctxErr error) ([]byte, error) {

// The user specified an authorization header so this is very likely a
// gRPC Web call from the UI. But we only attach the macaroon if the
Expand All @@ -426,10 +463,10 @@ func (p *rpcProxy) basicAuthToMacaroon(basicAuth, requestURI string) ([]byte,
// from the lnd backend.
authHeaderParts := strings.Split(basicAuth, " ")
if len(authHeaderParts) != 2 {
return nil, nil
return nil, ctxErr
}
if authHeaderParts[1] != p.basicAuth {
return nil, nil
return nil, ctxErr
}

var (
Expand Down Expand Up @@ -473,7 +510,20 @@ func (p *rpcProxy) basicAuthToMacaroon(basicAuth, requestURI string) ([]byte,
// If we have a super macaroon, we can use that one directly since it
// will contain all permissions we need.
case len(p.superMacaroon) > 0:
return hex.DecodeString(p.superMacaroon)
superMacData, err := hex.DecodeString(p.superMacaroon)

// Make sure we can avoid running into an empty macaroon here if
// something went wrong with the decoding process (if we're
// still starting up).
if err != nil {
return nil, &proxyErr{
proxyContext: "supermacaroon",
wrapped: fmt.Errorf("couldn't decode "+
"super macaroon: %v", err),
}
}

return superMacData, nil

// If we have macaroon data directly, just encode them. This could be
// for initial requests to lnd while we don't have the super macaroon
Expand All @@ -489,7 +539,10 @@ func (p *rpcProxy) basicAuthToMacaroon(basicAuth, requestURI string) ([]byte,
return readMacaroon(lncfg.CleanAndExpandPath(macPath))
}

return nil, fmt.Errorf("unknown macaroon to use")
return nil, &proxyErr{
proxyContext: "auth",
wrapped: fmt.Errorf("unknown macaroon to use"),
}
}

// dialBufConnBackend dials an in-memory connection to an RPC listener and
Expand Down
36 changes: 31 additions & 5 deletions terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -706,9 +706,16 @@ func (g *LightningTerminal) ValidateMacaroon(ctx context.Context,
"syncing")
}

return g.faradayServer.ValidateMacaroon(
err = g.faradayServer.ValidateMacaroon(
ctx, requiredPermissions, fullMethod,
)
if err != nil {
return &proxyErr{
proxyContext: "faraday",
wrapped: fmt.Errorf("invalid macaroon: %v",
err),
}
}

case isLoopURI(fullMethod):
// In remote mode we just pass through the request, the remote
Expand All @@ -723,9 +730,16 @@ func (g *LightningTerminal) ValidateMacaroon(ctx context.Context,
"syncing")
}

return g.loopServer.ValidateMacaroon(
err = g.loopServer.ValidateMacaroon(
ctx, requiredPermissions, fullMethod,
)
if err != nil {
return &proxyErr{
proxyContext: "loop",
wrapped: fmt.Errorf("invalid macaroon: %v",
err),
}
}

case isPoolURI(fullMethod):
// In remote mode we just pass through the request, the remote
Expand All @@ -740,14 +754,26 @@ func (g *LightningTerminal) ValidateMacaroon(ctx context.Context,
"syncing")
}

return g.poolServer.ValidateMacaroon(
err = g.poolServer.ValidateMacaroon(
ctx, requiredPermissions, fullMethod,
)
if err != nil {
return &proxyErr{
proxyContext: "pool",
wrapped: fmt.Errorf("invalid macaroon: %v",
err),
}
}

case isLitURI(fullMethod):
_, err := g.rpcProxy.convertBasicAuth(ctx, fullMethod)
wrap := fmt.Errorf("invalid basic auth")
_, err := g.rpcProxy.convertBasicAuth(ctx, fullMethod, wrap)
if err != nil {
return err
return &proxyErr{
proxyContext: "lit",
wrapped: fmt.Errorf("invalid auth: %v",
err),
}
}
}

Expand Down