Skip to content

Commit

Permalink
Add middleware factory function for Echo
Browse files Browse the repository at this point in the history
- Also add an Echo example, directly as go file in a new example directory instead of in the README

Closes #2
  • Loading branch information
philippgille committed Aug 4, 2018
1 parent 1c004f1 commit 6e95c1a
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 6 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

## Own

# lnd connection files
invoice.macaroon
tls.cert
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Middlewares for:
- [X] [net/http](https://golang.org/pkg/net/http/) `Handler`
- Compatible with routers like [gorilla/mux](https://github.com/gorilla/mux) and [chi](https://github.com/go-chi/chi)
- [X] [Gin](https://github.com/gin-gonic/gin)
- [ ] [Echo](https://github.com/labstack/echo)
- [X] [Echo](https://github.com/labstack/echo)

An API gateway is on the roadmap as well, which you can use to monetize your API that's written in *any* language, not just in Go.

Expand Down Expand Up @@ -157,10 +157,15 @@ func main() {
r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})

r.Run() // listen and serve on 0.0.0.0:8080
}
```

### Echo

See [example](examples/echo/main.go)

Related Projects
----------------

Expand Down
12 changes: 8 additions & 4 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,28 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
vNext
-----

Enhancements:

- Added: `pay.NewEchoMiddleware(...)` - A middleware factory function for [Echo](https://github.com/labstack/echo) (issue [#2](https://github.com/philippgille/ln-paywall/issues/2))

v0.2.0 (2018-07-29)
-------------------

Enhancements:

- Added: Interface `pay.StorageClient` - an abstraction for multiple storage clients, which allows you to write your own storage client for storing the preimages that have already been used as payment proof in a request
- Added: Interface `pay.StorageClient` - an abstraction for multiple storage clients, which allows you to write your own storage client for storing the preimages that have already been used as payment proof in a request (issue [#1](https://github.com/philippgille/ln-paywall/issues/1))
- Methods `WasUsed(string) (bool, error)` and `SetUsed(string) error`
- Added: Struct `pay.RedisClient` - implements the `StorageClient` interface
- Added: Struct `pay.RedisClient` - implements the `StorageClient` interface (issue [#1](https://github.com/philippgille/ln-paywall/issues/1))
- Factory function `NewRedisClient(...)`
- Added: Var `pay.DefaultRedisOptions` - a `Redisoptions` object with default values
- Added: Struct `pay.GoMap` - implements the `StorageClient` interface
- Added: Struct `pay.GoMap` - implements the `StorageClient` interface (issue [#1](https://github.com/philippgille/ln-paywall/issues/1))
- Factory function `NewGoMap()`
- Improved: Increased middleware performance and decreased load on the connected lnd when invalid requests with the `x-preimage` header are received (invalid because the preimage was already used) - Instead of first getting a corresponding invoice for a preimage from the lnd and *then* checking if the preimage was used already, the order of these operations was switched, because then, if the preimage was already used, no request to lnd needs to be made anymore.
- Improved: All fields of the struct `pay.RedisOptions` are now optional

Breaking changes:

- Changed: `pay.NewHandlerFuncMiddleware(...)`, `pay.NewHandlerMiddleware(...)` and `pay.NewGinMiddleware(...)` now take a `ln.StorageClient` instead of a `*redis.Client` as parameter
- Changed: `pay.NewHandlerFuncMiddleware(...)`, `pay.NewHandlerMiddleware(...)` and `pay.NewGinMiddleware(...)` now take a `ln.StorageClient` instead of a `*redis.Client` as parameter (issue [#1](https://github.com/philippgille/ln-paywall/issues/1))
- Changed: `ln.CheckPreimage(...)` was renamed to `ln.CheckInvoice(...)` and doesn't check the storage anymore. The `ln` methods are supposed to just handle lightning related things and nothing else. This shouldn't affect anyone, because `ln` is meant to be used only internally.
- Removed: Package `lnrpc` - Instead of using our own generated lnd gRPC Go file, import the one from Lightning Labs. This shouldn't affect anyone, because it was meant to be used only internally.

Expand Down
32 changes: 32 additions & 0 deletions examples/echo/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package main

import (
"net/http"

"github.com/labstack/echo"

"github.com/philippgille/ln-paywall/pay"
)

func main() {
e := echo.New()

// Configure and use middleware
invoiceOptions := pay.InvoiceOptions{
Amount: 1,
Memo: "Ping API call",
}
lndOptions := pay.LNDoptions{
Address: "123.123.123.123:10009",
CertFile: "tls.cert",
MacaroonFile: "invoice.macaroon",
}
redisClient := pay.NewRedisClient(pay.DefaultRedisOptions) // Connects to localhost:6379
e.Use(pay.NewEchoMiddleware(invoiceOptions, lndOptions, redisClient, nil))

e.GET("/ping", func(c echo.Context) error {
return c.String(http.StatusOK, "pong")
})

e.Logger.Fatal(e.Start(":8080")) // Start server
}
71 changes: 70 additions & 1 deletion pay/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"os"

"github.com/gin-gonic/gin"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"

"github.com/philippgille/ln-paywall/ln"
)
Expand Down Expand Up @@ -107,7 +109,6 @@ func NewGinMiddleware(invoiceOptions InvoiceOptions, lndOptions LNDoptions, stor
ctx.Abort()
} else {
stdOutLogger.Printf("Sending invoice in response: %v", invoice)
// Note: w.Header().Set(...) must be called before w.WriteHeader(...)!
ctx.Header("Content-Type", "application/vnd.lightning.bolt11")
ctx.Status(http.StatusPaymentRequired)
// The actual invoice goes into the body
Expand Down Expand Up @@ -139,6 +140,74 @@ func NewGinMiddleware(invoiceOptions InvoiceOptions, lndOptions LNDoptions, stor
}
}

// NewEchoMiddleware returns an Echo middleware in the form of an echo.MiddlewareFunc.
func NewEchoMiddleware(invoiceOptions InvoiceOptions, lndOptions LNDoptions, storageClient StorageClient, skipper middleware.Skipper) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
if skipper == nil {
skipper = middleware.DefaultSkipper
}
return func(ctx echo.Context) error {
if skipper(ctx) {
return next(ctx)
}
// Check if the request contains a header with the preimage that we need to check if the requester paid
preimage := ctx.Request().Header.Get("x-preimage")
if preimage == "" {
// Generate the invoice
invoice, err := ln.GenerateInvoice(invoiceOptions.Amount, invoiceOptions.Memo,
lndOptions.Address, lndOptions.CertFile, lndOptions.MacaroonFile)
if err != nil {
errorMsg := fmt.Sprintf("Couldn't generate invoice: %+v", err)
log.Println(errorMsg)
return &echo.HTTPError{
Code: http.StatusBadRequest,
Message: errorMsg,
Internal: err,
}
} else {
stdOutLogger.Printf("Sending invoice in response: %v", invoice)
ctx.Response().Header().Set("Content-Type", "application/vnd.lightning.bolt11")
ctx.Response().Status = http.StatusPaymentRequired
// The actual invoice goes into the body
ctx.Response().Write([]byte(invoice))
return &echo.HTTPError{
Code: http.StatusPaymentRequired,
Message: invoice,
}
}
} else {
// Check if the provided preimage belongs to a settled API payment invoice and that it wasn't already used and store used preimages
ok, err := handlePreimage(preimage, lndOptions.Address, lndOptions.CertFile, lndOptions.MacaroonFile, storageClient)
if err != nil {
errorMsg := fmt.Sprintf("An error occured during checking the preimage: %+v", err)
log.Printf("%v\n", errorMsg)
return &echo.HTTPError{
Code: http.StatusBadRequest,
Message: errorMsg,
Internal: err,
}
} else {
if !ok {
errorMsg := "The provided preimage is invalid"
log.Printf("%v: %v\n", errorMsg, preimage)
return &echo.HTTPError{
Code: http.StatusBadRequest,
Message: errorMsg,
Internal: err,
}
} else {
preimageHash, err := ln.HashPreimage(preimage)
if err == nil {
stdOutLogger.Printf("The provided preimage is valid. Continuing to the next HandlerFunc. Preimage hash: %v\n", preimageHash)
}
}
}
}
return next(ctx)
}
}
}

// StorageClient is an abstraction for different storage client implementations.
// A storage client must only be able to check if a preimage was already used for a payment bofore
// and to store a preimage that was used before.
Expand Down

0 comments on commit 6e95c1a

Please sign in to comment.