Golang echo middleware redirect with empty page - go-echo

I testing a redirect about echo when I just use the method to redirect it can show the login.html page successfully, but if I use a middleware test, it always shows empty page of login.html, what am I missing?
e.Group("*.html", func(next echo.HandlerFunc) echo.HandlerFunc { //1
return func(c echo.Context) error {
uri := c.Request().URL.String()
log.Println("uri:" + uri)
if uri != "/login.html" && uri != "/favicon.ico" {
c.Redirect(http.StatusSeeOther, "login.html")
return nil
}
return nil
}
})
e.Use(session.Middleware(sessions.NewCookieStore([]byte("secret"))))
e.GET("/aa", func(c echo.Context) error { //2
c.Redirect(http.StatusSeeOther, "login.html")
return nil
})

I see that you are missing the call of next to continue the request chain.
See the example here: https://echo.labstack.com/cookbook/middleware/
// ServerHeader middleware adds a `Server` header to the response.
func ServerHeader(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Response().Header().Set(echo.HeaderServer, "Echo/3.0")
return next(c)
}
}
See the return next(c), it continues to process the request throw all middlewares and eventually the login.html static handler.
As you are not calling, it stops the chain and does nothing.

Related

How to cause Buffalo transaction middleware to commit?

In trying to use the buffalo-pop/pop/popmw Transaction middleware, I am not having success writing to the database. No errors are returned, and the debug output shows the SQL statements, but the updates and inserts are not committed.
The handler looks like:
func MyHandler(c buffalo.Context) error {
tx, ok := c.Value("tx").(*pop.Connection)
if !ok {
return errors.New("no transaction found")
}
f := models.File{
Name: "file.txt",
}
if err := tx.Create(&f); err != nil {
return err
}
return nil
}
app.go:
func App() *buffalo.App {
...
app.GET("/myhandler", MyHandler)
app.Use(popmw.Transaction(models.DB))
...
}
If I use DB, _ := pop.Connect("development") for my connection, it works correctly. I also observed that the autoincrement value on the table changes each time this handler is hit.
In the real app, we can't call c.Render to report a response code because we are using gqlgen as the http handler. It looks like this:
func GQLHandler(c buffalo.Context) error {
h := handler.GraphQL(gqlgen.NewExecutableSchema(gqlgen.Config{Resolvers: &gqlgen.Resolver{}}))
newCtx := context.WithValue(c.Request().Context(), "BuffaloContext", c)
h.ServeHTTP(c.Response(), c.Request().WithContext(newCtx))
return nil
}
One of the features of the Pop Middleware for Buffalo is to wrap the action and the middlewares below in the stack inside a DB transaction. Here are the conditions for an auto-commit from the Pop Middleware:
Commit if there was no error executing the middlewares and action; and the response status is a 2xx or 3xx.
Rollback otherwise.
From Buffalo integration with Pop.
So, make sure no error is returned in either your action or in a middleware of the stack; and the produced response status is 200-ish or 300-ish.
If Buffalo receives no response code via a call to c.Render, the Transaction middleware treats the request as non-successful. Since the context of this question is GraphQL using gqlgen, and c.Render cannot be used, I found that explicitly closing the transaction works. Something like this:
func GQLHandler(c buffalo.Context) error {
gqlSuccess := true
h := handler.GraphQL(gqlgen.NewExecutableSchema(gqlgen.Config{Resolvers: &gqlgen.Resolver{}})),
handler.ErrorPresenter(
func(ctx context.Context, e error) *gqlerror.Error {
gqlSuccess = false
return graphql.DefaultErrorPresenter(ctx, e)
}))
newCtx := context.WithValue(c.Request().Context(), "BuffaloContext", c)
h.ServeHTTP(c.Response(), c.Request().WithContext(newCtx))
if !gqlSuccess {
return nil
}
tx, ok := c.Value("tx").(*pop.Connection)
if !ok {
return errors.New("no transaction found")
}
return tx.TX.Commit()
}

How to redirect all 404 errors

How would I catch all 404 errors and redirect to /app/index.html (I'm trying to satisfy Angular's need to forward all not found resources to index.html) ? I thought implementing my own middleware would work, but not sure I'm doing it right
public final class ForwardToAngularMiddleware: Middleware {
public func respond(to req: Request, chainingTo next: Responder) throws -> EventLoopFuture<Response> {
do {
let response: Future<Response> = try next.respond(to: req)
return response
} catch let error as AbortError {
if error.status == .notFound && req.http.url.path.starts(with: "/app") {
return Future.map(on: req) { req.redirect(to: "/index.html") }
}
throw error
}
}
}
My program never hits the catch block no matter what URL I send. I am configuring my middleware like this:
middlewares.use(FileMiddleware.self)
middlewares.use(ForwardToAngularMiddleware())
middlewares.use(ErrorMiddleware.self)
middlewares.use(SessionsMiddleware.self)
services.register(middlewares)
You may be hitting a couple of issues here. First, the abort error could be being thrown in a future, in which case you need to add a catchMap to the next.respond(to:) call to catch that case.
It also may not throw (though this is unlikely), so you can try unwrapping the response and checking the status code.
Have you put a breakpoint in to see if it ever hits it etc?

Get the HTTPURLResponse from a Siesta Response

I'm fighting against REST API that performs a 304 redirect; what I need is to take the destination URL of the redirect and open it with the browser (I know, it's a little of a perversion). I successfully intercepted the redirect, thanks to this nice lad reversepanda:
https://github.com/bustoutsolutions/siesta/issues/210
but still I didn't figure out how to take the redirect url in the callback of the GET request (success or failure)
resource.load().onSuccess{ response in
//HERE I WOULD LIKE TO TAKE THE URL OF THE REDIRECT
//(if I print the response I can see the HTML of the destination web page where the user should land)
}.onFailure{ error in
//using 'completionHandler(nil)' in the 'willPerformHTTPRedirection' method of the delegate, brings me here
}
Any suggestion on how I could resolve this problem?
Thank you!
Take a look inside RequestChain.swift, it has some comments with example which can help. I believe you can do something like:
func redirectRequest() -> Request {
return self.yourAnotherRequest(onSuccess: {
}, onFailure: { error in
})
}
func yourRequest(request: Siesta.Request) -> Request {
return request.chained {
guard case .failure(let error) = $0.response,
error.httpStatusCode == 401 else {
return .useThisResponse
}
return .passTo(
self.redirectRequest().chained {
if case .failure = $0.response {
return .useThisResponse
} else {
return .passTo(request.repeated())
}
}
)
}
}
You can search more examples with keywords chained, useThisResponse and passTo in Siesta sources.
Please, let us know if it helps to solve your issue and it would be nice to see your final solution.

go-restful + JWT authentication

I'm trying to plug JWT authentication within a very simple go service written with go-restful.
The code is very similar to:
package main
import (
"github.com/emicklei/go-restful"
"log"
"net/http"
)
type User struct {
Id, Name string
}
type UserList struct {
Users []User
}
func getAllUsers(request *restful.Request, response *restful.Response) {
log.Printf("getAllUsers")
response.WriteEntity(UserList{[]User{{"42", "Gandalf"}, {"3.14", "Pi"}}})
}
func NewUserService() *restful.WebService {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML)
ws.Route(ws.GET("").To(getAllUsers))
return ws
}
func main() {
restful.Add(NewUserService())
log.Printf("start listening on localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
where restful.Request is a wrapper around http.Request.
That being said, it might be possible to use the Auth0 jwt middleware.
But as a golang newbie, I'm a bit lost in the plumbing process. I see that I must use a Filter function like
ws.Filter(jwtAuthentication)
where
func jwtAuthentication(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
// Jwt Magic goes here \o
chain.ProcessFilter(req, resp)
}
But I don't figure how and where should I instanciate the JWT middleware.
Here is the example of filter implementation using auth0/go-jwt-middleware. In real life you probably want to avoid creating new instance of jwtMiddleware every time.
func jwtAuthentication(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
return []byte("My Secret"), nil
},
SigningMethod: jwt.SigningMethodHS256,
})
if err := jwtMiddleware.CheckJWT(resp.ResponseWriter, req.Request); err != nil {
logger.Errorf("Authentication error: %v", err)
}
chain.ProcessFilter(req, resp)
}
After the filter the parsed token will be in the context (auth0/go-jwt-middleware uses gorilla/context). Default context key is user.
Note: when JWTMiddleware.SigningMethod is set, the middleware verifies that tokens are signed with the specific signing algorithm.
If the signing method is not constant, the ValidationKeyGetter callback can be used to implement additional checks.
Important to avoid security issues described here.
Here is example of Login API to generate Token, and JWT Authentication filter to check authentication
import (
"os"
"strings"
"github.com/dgrijalva/jwt-go"
"golang.org/x/crypto/bcrypt"
)
type Token struct {
UserId uint
Username string
jwt.StandardClaims
}
type Account struct {
ID uint
Email string
Password string
Token string
}
func Login(request *restful.Request, response *restful.Response) {
account := &Account{ID: 1, Email: "test#email.com" }
// TODO - account should be pulled from database
tk := &Token{ UserId: account.ID }
token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk)
tokenString, _ := token.SignedString([]byte("JWT-SECRET-GOES-RIGHT-HERE"))
account.Token = tokenString
account.Password = ''
response.WriteEntity(account)
}
func JwtAuthentication(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
tokenHeader := req.Request.HeaderParameter("Authorization")
if tokenHeader == "" {
resp.WriteErrorString(http.StatusForbidden, "Not Authorized")
return
}
splitted := strings.Split(tokenHeader, " ")
if len(splitted) != 2 {
resp.WriteErrorString(http.StatusForbidden, "Not Authorized")
return
}
tokenPart := splitted[1]
tk := &Token{}
token, err := jwt.ParseWithClaims(tokenPart, tk, func(token *jwt.Token) (interface{}, error) {
return []byte("JWT-SECRET-GOES-RIGHT-HERE"), nil
})
if err != nil { //Malformed token, returns with http code 403 as usual
resp.WriteErrorString(http.StatusForbidden, "Not Authorized")
return
}
if !token.Valid { //Token is invalid, maybe not signed on this server
resp.WriteErrorString(http.StatusForbidden, "Not Authorized")
return
}
chain.ProcessFilter(req, resp)
}
And then apply filter
ws.Filter(JwtAuthentication)

Break for loop inside a web request (in swift)

Inside my for loop i run a web request. Now if theres an error in the web request i want to break the for loop, however i get this error:
break is only allowed inside a loop or switch I've also tried naming my for loop like I've seen such as myLoopName : for(...) then calling it with break myLoopName but then i get the same error.
Here is an example of how my code looks currently
myLoopName : for(...) {
...
SRWebClient.POST(someUrlString)
.data(...)
.send({(response:AnyObject!, status:Int) -> Void in
//process success response
},failure:{(error:NSError!) -> Void in
//process failure response
println(error)
//where break must occur
})
}
How do i break the loop inside a web request?
I would do it using a function when dealing with closures and breaks:
func loop() {
SRWebClient.POST(someUrlString).data().send( { response, status in
loop()
},
failure:{ (error:NSError!) -> Void in
println(error)
}
}
I used return instead of break and it worked perfectly
func loop() {
SRWebClient.POST(someUrlString).data().send( { response, status in
loop()
},
failure:{ (error:NSError!) -> Void in
println(error)
return
}
}