I'm new to golang, and have followed this (https://auth0.com/blog/authentication-in-golang/) auth0 guide, for setting up a go rest api.
I'm struggeling with converting to Fiber, and in the same time putting my functions that are being called by routes, out to seperate files.
Currently my main file looks like this:
func main() {
r := mux.NewRouter()
r.Handle("/", http.FileServer(http.Dir("./views/")))
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
r.Handle("/posts", config.JwtMiddleware.Handler(GetPosts)).Methods("GET")
//r.Handle("/products/{slug}/feedback", jwtMiddleware.Handler(AddFeedbackHandler)).Methods("POST")
// For dev only - Set up CORS so React client can consume our API
corsWrapper := cors.New(cors.Options{
AllowedMethods: []string{"GET", "POST"},
AllowedHeaders: []string{"Content-Type", "Origin", "Accept", "*"},
})
http.ListenAndServe(":8080", corsWrapper.Handler(r))
}
var GetPosts= http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
collection, err := config.GetMongoDbCollection(dbName, collectionName)
if err != nil {
fmt.Println("Error")
}else{
fmt.Println(collection)
//findOptions := options.Find()
cursor, err := collection.Find(context.Background(), bson.M{})
if err != nil {
log.Fatal(err)
}
var posts[]bson.M
if err = cursor.All(context.Background(), &posts); err != nil {
log.Fatal(err)
}
fmt.Println(posts)
payload, _ := json.Marshal(posts)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(payload))
}
})
So I would like to convert from: r := mux.NewRouter() to fiber and in the same time move my GetPosts function out in a seperate file. When doing this, I can't seem to continue calling my jwtMiddleware.
I have tried this package: https://github.com/Mechse/fiberauth0 but it seems like its broken. At least I can call protected routes without supplying jwt tokens in my header.
You can simply convert 'net/http' style middleware handlers with the provided adaptor package(https://github.com/gofiber/adaptor). Note you need to make some changes to the function signature provided by auth0 but this works;
// EnsureValidToken is a middleware that will check the validity of our JWT.
func ensureValidToken(next http.Handler) http.Handler {
issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/")
if err != nil {
log.Fatalf("Failed to parse the issuer url: %v", err)
}
provider := jwks.NewCachingProvider(issuerURL, 5*time.Minute)
jwtValidator, err := validator.New(
provider.KeyFunc,
validator.RS256,
issuerURL.String(),
[]string{os.Getenv("AUTH0_AUDIENCE")},
validator.WithCustomClaims(
func() validator.CustomClaims {
return &CustomClaims{}
},
),
validator.WithAllowedClockSkew(time.Minute),
)
if err != nil {
log.Fatalf("Failed to set up the jwt validator")
}
errorHandler := func(w http.ResponseWriter, r *http.Request, err error) {
log.Printf("Encountered error while validating JWT: %v", err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"message":"Failed to validate JWT."}`))
}
middleware := jwtmiddleware.New(
jwtValidator.ValidateToken,
jwtmiddleware.WithErrorHandler(errorHandler),
)
return middleware.CheckJWT(next)
}
var EnsureValidToken = adaptor.HTTPMiddleware(ensureValidToken)
app := fiber.New()
app.Use(EnsureValidToken)
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
app.Listen(":3000")
Related
i am building an promethues exporter in golang, the url to the exporter will be http://exporter-ip:9000/unique-id/metrics.
By parsing the url in ProcessParameters() function i am getting unique-id and with unique-id i am getting ip,username,password.
how can i pass IP, Username, Password from ProcessParameters() middleware function to Collect() function.
There variables are request scoped
func (collector *Collector) Collect(ch chan<- prometheus.Metric) {
//need IP,Username & Password here.
}
func ProcessParameters(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Print("Executing middlewareOne")
DeviceID = strings.Split(r.URL.Path, "/")[1] //getting unique ID from the URL and verifying if that id is valid
_, ok := util.Devices[DeviceID] //if device id is not present in map, return StatusForbidden error in if block.
if !ok{
errMsg := "Device not found"
http.Error(w, errMsg, http.StatusForbidden)
log.Println(errMsg)
w.WriteHeader(http.StatusForbidden)
w.Header().Set("Content-Type", "application/json")
resp := make(map[string]string)
resp["message"] = "Forbidden"
jsonResp, err := json.Marshal(resp)
if err != nil {
log.Fatalf("Error happened in JSON marshal. Err: %s", err)
}
w.Write(jsonResp)
} else { //if id is present pass controller final handler(deviceHandler)
// tried setting it to request context also how to access it from collect() func
ctx := context.WithValue(r.Context(), "IP", util.Devices["10.0.0.1"])
context.WithValue(r.Context(), "UserName", util.Devices["user1"])
context.WithValue(r.Context(), "Password", util.Devices["pass1"])
next.ServeHTTP(w, r.WithContext(ctx))
}
})
}
func main() {
collector := metricsCollector()
registry := prometheus.NewRegistry()
registry.Register(collector)
deviceHandler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
mux := http.NewServeMux()
mux.Handle("/", ProcessParameters(deviceHandler)) // how can pass variables from ProcessParameters() middleware handler to deviceHandler
err := http.ListenAndServe(":9090", mux)
log.Fatal(err)
}
I did all of this: Google OAuth 2 authorization - Error: redirect_uri_mismatch
I have added auth Uri's, but still doesn't work
"redirect_uris":["https://localhost:8080","http://localhost:8080","http://localhost:8080/google_login","http://localhost:8080/google_callback","https://localhost","http://localhost"]
But Im still getting this error message:
My main.go:
func main() {
// load configs
godotenv.Load(".env")
config.SetupConfig()
// create a router
mux := http.NewServeMux()
// define routes
mux.HandleFunc("/google_login", controllers.GoogleLogin)
mux.HandleFunc("/google_callback", controllers.GoogleCallback)
// run server
log.Println("started server on :: http://localhost:8080/")
if oops := http.ListenAndServe(":8080", mux); oops != nil {
log.Fatal(oops)
}
}
contants.go:
func SetupConfig() *oauth2.Config {
conf := &oauth2.Config{
ClientID: os.Getenv("NoteClientId"),
ClientSecret: os.Getenv("NoteClientSecret"),
RedirectURL: "http://localhost:8080/google/callback",
Scopes: []string{
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
},
Endpoint: google.Endpoint,
}
return conf
}
google.go:
func GoogleLogin(res http.ResponseWriter, req *http.Request) {
googleConfig := config.SetupConfig()
url := googleConfig.AuthCodeURL("randomstate")
http.Redirect(res, req, url, http.StatusSeeOther)
}
func GoogleCallback(res http.ResponseWriter, req *http.Request) {
state := req.URL.Query()["state"][0]
if state != "randomstate" {
fmt.Fprintln(res, "states dont match")
return
}
code := req.URL.Query()["code"][0]
googleConfig := config.SetupConfig()
token, err := googleConfig.Exchange(context.Background(), code)
if err != nil {
fmt.Fprintln(res, "Code-Token Exchange failed")
}
resp, err := http.Get("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + token.AccessToken)
if err != nil {
fmt.Fprintln(res, "User data fetch failed")
}
userData, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Fprintln(res, "Json Parsing Failed")
}
fmt.Fprintln(res, string(userData))
}
None of these URIs
["https://localhost:8080","http://localhost:8080","http://localhost:8080/google_login","http://localhost:8080/google_callback","https://localhost","http://localhost"]
march the redirect URI you have configured in constants.go
RedirectURL: "http://localhost:8080/google/callback",
Change one or the other so that they match
I'm trying to test a Go function which performs a call to an external service. Here's the function:
func (gs *EuGameService) retrieveGames(client model.HTTPClient) (model.EuGamesResponse, error) {
req, err := http.NewRequest(http.MethodGet, gs.getGamesEndpoint, nil)
if err != nil {
log.Fatal("Error while creating request ", err)
return nil, err
}
resp, err := client.Do(req)
if err != nil {
log.Fatal("Error while retrieving EU games", err)
return nil, err
}
var euGames model.EuGamesResponse
decoder := json.NewDecoder(resp.Body)
decoder.Decode(&euGames)
return euGames, nil
}
to properly test it, I'm trying to inject a mock client.
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
type mockClient struct{}
func (mc *mockClient) Do(req *http.Request) (*http.Response, error) {
mock, _ := os.Open("../stubs/eugames.json")
defer mock.Close()
r := ioutil.NopCloser(bufio.NewReader(mock))
return &http.Response{
Status: string(http.StatusOK),
StatusCode: http.StatusOK,
Body: r,
}, nil
}
the file eugames.json contains a couple of games. But for some reason, the body is always empty! What am I missing here? I tried to use a constant with the file content and it works, games are decoded correctly. So I'm assuming there's a problem with my use of the file.
func indexHandler(w http.ResponseWriter, req *http.Request) {
session, err := store.Get(req, sessionName)
if err != nil {
log.WithError(err).Error("bad session")
http.SetCookie(w, &http.Cookie{Name: sessionName, MaxAge: -1, Path: "/"})
}
err = views.ExecuteTemplate(w, "index.html", session.Values)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
All my handlers use Gorilla sessions. How can I avoid store.Getting the session in each of my handlers, i.e. code repeating?
Another issue, is there a better way to give the template the session values, other that an explicit way like:
err = views.ExecuteTemplate(w, "index.html",
struct {
Session map[interface{}]interface{},
...other handler specific data for the template
}{
session.Values,
...
})
Code samples originate from https://github.com/kaihendry/internal-google-login
For the middleware you can define a function that takes a handler as an argument and returns another handler as its result.
func sessionMiddleware(h http.HandlerFunc) http.HandlerFunc {
// ...
}
The returned handler can store.Get the session, if it's not present return the error, if it is present store the session into the request's context and then call the actual handler.
func sessionMiddleware(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, err := store.Get(r, sessionName)
if err != nil {
log.WithError(err).Error("bad session")
http.SetCookie(w, &http.Cookie{Name: sessionName, MaxAge: -1, Path: "/"})
return
}
r = r.WithContext(context.WithValue(r.Context(), "session", session))
h(w, r)
}
}
Now your handlers will still need to "get" the session value from the context however any handler that is wrapped by sessionMiddleware can assume, when it's executed, that a session is present in the context and therefore it can skip the error checking.
func indexHandler(w http.ResponseWriter, req *http.Request) {
session := req.Context().Value("session").(*sessions.Session)
err := views.ExecuteTemplate(w, "index.html", session.Values)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
And to register the handler you would do:
app.HandleFunc("/", sessionMiddleware(indexHandler))
If this is still too much code repetition for your taste you can pass the session directly to your handlers however you'll have to change their signature.
type SessionHandler func(w http.ResponseWriter, r *http.Request, s *session.Session)
Then update your handlers.
func indexHandler(w http.ResponseWriter, req *http.Request, s *session.Session) {
err := views.ExecuteTemplate(w, "index.html", s.Values)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
And you can define the middleware part on the SessionHandler type as a method.
func (h SessionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
session, err := store.Get(r, sessionName)
if err != nil {
log.WithError(err).Error("bad session")
http.SetCookie(w, &http.Cookie{Name: sessionName, MaxAge: -1, Path: "/"})
return
}
h(w, r, session)
}
And then to register the handler you would do:
app.Handle("/", SessionHandler(indexHandler))
I am writing test for an elasticsearch middleware, where I am using a function to build test servers in which I pass a slice of configuration structs for each tests and in a handler function they are iterated upon and the expected response is written to the response writer. This is my function.
func newMockClient(url string) (*elasticsearch, error) {
client, err := elastic.NewSimpleClient(elastic.SetURL(url))
if err != nil {
return nil, fmt.Errorf("error while initializing elastic client: %v", err)
}
es := &elasticsearch{
url: url,
client: client,
}
return es, nil
}
type ServerSetup struct {
Method, Path, Body, Response string
HTTPStatus int
}
func buildTestServer(t *testing.T, setups []*ServerSetup) *httptest.Server {
handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestBytes, _ := ioutil.ReadAll(r.Body)
requestBody := string(requestBytes)
matched := false
for _, setup := range setups {
if r.Method == setup.Method && r.URL.EscapedPath() == setup.Path {
matched = true
if setup.HTTPStatus == 0 {
w.WriteHeader(http.StatusOK)
} else {
w.WriteHeader(setup.HTTPStatus)
}
_, err := w.Write([]byte(setup.Response))
if err != nil {
t.Fatalf("Unable to write test server response: %v", err)
}
}
}
if !matched {
t.Fatalf("No requests matched setup. Got method %s, Path %s, body %s", r.Method, r.URL.EscapedPath(), requestBody)
}
})
return httptest.NewServer(handlerFunc)
}
It is copied from github.com/github/vulcanizer. When I run a single test using this, it works fine. For e.g. this test
func TestCreateIndex(t *testing.T) {
setup := &ServerSetup{
Method: "PUT",
Path: "/test",
Response: `{"acknowledged": true, "shards_acknowledged": true, "index": "test"}`,
}
ts := buildTestServer(t, []*ServerSetup{setup})
es, _ := newMockClient(ts.URL)
err := es.createIndex(context.Background(), "test", nil)
if err != nil {
t.Fatalf("Index creation failed with error: %v\n", err)
}
}
But when I try to check different behaviours in a single test like this one I get an error http: multiple response.WriteHeader calls
func TestDeleteIndex(t *testing.T) {
setup := &ServerSetup{
Method: "DELETE",
Path: "/test",
Response: `{"acknowledged": true}`,
}
errSetup := &ServerSetup{
Method: "DELETE",
Path: "/test",
Response: `{"acknowledged": false}`,
}
ctx := context.Background()
ts := buildTestServer(t, []*ServerSetup{setup, errSetup})
defer ts.Close()
es, _ := newMockClient(ts.URL)
err := es.deleteIndex(ctx, "test")
if err != nil {
t.Fatalf("Index creation failed with error: %v\n", err)
}
err = es.deleteIndex(ctx, "test")
if err == nil {
t.Fatal("Expected error but not found")
}
}
I am guessing that it is because of the fact that when I run deleteIndex for the second time it pings the server again but the response writer has already been written to so it can't write anything else to it.
Is there anyway I can have a check at the beginning of my handler func like
handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if w != nil{
// clear data in response writer
}
// .........
}
I don't think what you are doing is the correct way to test your functionality. You need to separate your test to test-cases for checking different behaviours like that:
func Test_DeleteIndex(t *testing.T) {
t.Run("Should be ok with correct setup", func(t *testing.T) {
setup := &ServerSetup{
Method: "DELETE",
Path: "/test",
Response: `{"acknowledged": true}`,
}
ctx := context.Background()
ts := buildTestServer(t, []*ServerSetup{setup})
defer ts.Close()
es, _ := newMockClient(ts.URL)
err := es.deleteIndex(ctx, "test")
require.NoError(t, err)
})
t.Run("Shouldn't be ok with wrong setup", func(t *testing.T) {
setup := &ServerSetup{
Method: "DELETE",
Path: "/test",
Response: `{"acknowledged": false}`,
}
ctx := context.Background()
ts := buildTestServer(t, []*ServerSetup{setup})
defer ts.Close()
es, _ := newMockClient(ts.URL)
err := es.deleteIndex(ctx, "test")
require.Error(t, err)
})
}
The issue here is that for each request that the test server gets, the handler loops over all the ServerSetup structs checking for matches based on method and path, but does not break out of the loop upon finding a match.
So in your second test case, since you pass two setup structs with the same Method and Path, two setup cases will match an incoming request for DELETE /test, and the program will try to call WriteHeader on the ResponseWriter twice.
There are two ways I can think of to resolve this issue:
Option 1
If you want to have the server respond differently to successive calls of the same method and path combination, you can add an attribute to check if the ServerSetup instance has been used already, and avoid any setup structs that have already been used.
For example:
type ServerSetup struct {
Method, Path, Body, Response string
HTTPStatus int
HasBeenCalled bool
}
func buildTestServer(t *testing.T, setups []*ServerSetup) *httptest.Server {
handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestBytes, _ := ioutil.ReadAll(r.Body)
requestBody := string(requestBytes)
matched := false
for _, setup := range setups {
if setup.HasBeenCalled {
continue // skip those that have already been called
}
if r.Method == setup.Method && r.URL.EscapedPath() == setup.Path {
setup.HasBeenCalled = true
matched = true
if setup.HTTPStatus == 0 {
w.WriteHeader(http.StatusOK)
} else {
w.WriteHeader(setup.HTTPStatus)
}
_, err := w.Write([]byte(setup.Response))
if err != nil {
t.Fatalf("Unable to write test server response: %v", err)
}
}
if matched {
break // stop checking for matches if already found match
}
}
if !matched {
t.Fatalf("No requests matched setup. Got method %s, Path %s, body %s", r.Method, r.URL.EscapedPath(), requestBody)
}
})
return httptest.NewServer(handlerFunc)
}
Option 2
A slightly simpler way to resolve this issue would be to create separate test servers for each of these two cases, one for each setup struct, since they involve different results from the same method-path combination. More cleanly, you could separate these into two separate tests.
So you would end up with:
func TestDeleteIndex_Success(t *testing.T) {
setup := &ServerSetup{
Method: "DELETE",
Path: "/test",
Response: `{"acknowledged": true}`,
}
ctx := context.Background()
ts := buildTestServer(t, []*ServerSetup{setup})
defer ts.Close()
es, _ := newMockClient(ts.URL)
err := es.deleteIndex(ctx, "test")
if err != nil {
t.Fatalf("Index creation failed with error: %v\n", err)
}
}
func TestDeleteIndex_Error(t *testing.T) {
errSetup := &ServerSetup{
Method: "DELETE",
Path: "/test",
Response: `{"acknowledged": false}`,
}
ctx := context.Background()
ts := buildTestServer(t, []*ServerSetup{errSetup})
defer ts.Close()
es, _ := newMockClient(ts.URL)
err := es.deleteIndex(ctx, "test")
if err == nil {
t.Fatal("Expected error but not found")
}
}
You should also in the future avoid passing multiple ServerSetup structs with the same method-path combo in the future to avoid this error.