How to pass request scope variables to promethues handler function - go

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)
}

Related

Golang - Google OAuth 2 authorization - Error: redirect_uri_mismatch

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

Golang Fiber and Auth0

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")

Go - Mock http.Response body with a file

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.

How to pass json data as request parameter while mocking using gin framework

I have a function to create user which is working properly. Now I have to mock Prepare and SaveUser function inside CreateUser. But that CreateUser require json data as request parameter.
Below is my CreateUser function.
func (server *Server) CreateUser(c *gin.Context) {
errList = map[string]string{}
user := models.User{}
if err := c.ShouldBindJSON(&user); err != nil {
log.Println(err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) **//every time return from here with error -> invalid request**
return
}
user.Prepare()
userCreated, err := sqlstore.SaveUser(&user)
if err != nil {
formattedError := formaterror.FormatError(err.Error())
errList = formattedError
c.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
"error": errList,
})
return
}
c.JSON(http.StatusCreated, gin.H{
"status": http.StatusCreated,
"response": userCreated,
})
}
This is required json data as request parameter for above create user. I want to pass below data while mocking.
{"firstname":"test","email":"test#test.com"}
Below is test case to mock above create user function.
type UserMock struct {
mock.Mock
}
func (u *UserMock) Prepare() (string, error) {
args := u.Called()
return args.String(0), args.Error(1)
}
func (u *UserMock) SaveUser() (string, error) {
args := u.Called()
return args.String(0), args.Error(1)
}
func TestCreateUser(t *testing.T) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
**//how to json data as request parameter**
uMock := UserMock{}
uMock.On("Prepare").Return("mocktest", nil)
server := Server{}
server.CreateUser(c)
if w.Code != 201 {
t.Error("Unexpected status code found : ",w.Code)
}
}
Thanks in advance.
You need to add a strings.NewReader(string(myjson)) on a new request. Please check this and take it as a template on your current GIN Code.
// TestCreateUser new test
func TestCreateUser(t *testing.T) {
// Setup Recorder, TestContext, Router
router := getRouter(true)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
// set JSON body
jsonParam := `{"firstname":"test","email":"test#test.com"}`
// Mock HTTP Request and it's return
req, err := http.NewRequest("POST", "/user", strings.NewReader(string(jsonParam)))
// make sure request was executed
assert.NoError(t, err)
// Serve Request and recorded data
router.ServeHTTP(w, req)
// Test results
assert.Equal(t, 200, w.Code)
assert.Equal(t, nil, w.Body.String())
// check your response since nil isn't valid return
}

Handling an empty Golang gorilla/mux vars

I have the following controller function that is supposed to delete an item within a collection. I am using the gorilla/mux package. The problem arises when I try to handle the case where the user forgot to send an id on a DELETE request.
localhost:8000/movies/ or localhost:8000/movies
While an example of the proper request would be
localhost:8000/movies/3
Although params["id"] is empty, the execution never goes into the else block. I am not sure why.
func (c Controller) Delete(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var error Error
params := mux.Vars(r)
movieRepo := movieRepo.MovieRepository{}
if _, ok := params["id"]; ok {
id, err := strconv.Atoi(params["id"])
if err != nil {
error.Message = "Server error"
utils.SendError(w, http.StatusInternalServerError, error)
return
}
rowsDeleted, err := movieRepo.delete(db, id)
w.Header().Set("Content-Type", "text/plain")
successMessage(w, rowsDeleted)
} else {
fmt.Println("errrr..")
error.Message = "Id is not present."
errorMessage(w, http.StatusBadRequest, error)
return
}
}
}
EDIT
This is how the endpoint is registered on the router:
router.HandleFunc("/movies/{id}",controller.Delete(db)).Methods("DELETE")

Resources