Skip to content
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

How access portions of the request. #1209

Open
marvfinsy opened this issue Nov 16, 2024 · 11 comments
Open

How access portions of the request. #1209

marvfinsy opened this issue Nov 16, 2024 · 11 comments

Comments

@marvfinsy
Copy link

marvfinsy commented Nov 16, 2024

From the context I see u have a method to get Host. Query parameters etc.

How do i access other parts of the request like:

  1. Full url path?
  2. url path minus scheme and host?
  3. Access to request headers.

I need to get the path of the request without the scheme and host parts. Was going to create custom middleware to do this. It would add a custom header to the request with that information. Once in my route i'd access the header i added. yuk!

Of course If i can't read the generated header in my route he above is useless. Really hate the idea of altering original request adding self-generated headers.

Do i really need to do all the above to read request path information or to read headers?

thx ~Marvin
marvinfoster@finsync.com
mmarvb7@gmail.com

@Umang01-hash
Copy link
Contributor

Hi @marvfinsy ,

Thanks for reaching out with your feature request for the GoFr framework. We’d like to understand your needs better:

URL Path Access: Since the URL path is directly mapped to the handler, could you explain why a separate method is necessary?

Request Headers: We currently have methods to access authentication-related headers. Could you please provide more details on the type of headers you need to access and the specific use case?

Understanding your specific needs will help us provide the best solution. Looking forward to your response.

@marvfinsy
Copy link
Author

Yes i could set a variable = to the mapped path...

But my original idea was to map every URL path to the same handler. That handler can get the URL path from the http.Request.Url object. 80% to 90% of business logic seems to be the same.

There will be a "list" of acceptable headers. This common handler would also need to read those headers from the request.

@Umang01-hash Umang01-hash mentioned this issue Nov 19, 2024
4 tasks
@Umang01-hash
Copy link
Contributor

@marvfinsy Thank you for sharing your feedback regarding header access. While we understand your idea of mapping every URL path to the same handler and reading headers from the request, we would love to learn more about your specific use case to ensure that our implementation aligns with your requirements.

Could you please provide an example or scenario where this functionality would be particularly useful? For instance, how would you envision using the list of acceptable headers, and what kind of business logic would depend on this header-based processing?

Your insights will help us evaluate and prioritize this feature to best serve your needs.

Looking forward to hearing from you!

@devorbitus
Copy link

devorbitus commented Nov 20, 2024

@Umang01-hash an example application would be a Zoom "Recording Complete" webhook that requires validation of a specific timestamp from the header and then returning a specific payload

as described here:

https://developers.zoom.us/docs/api/rest/webhook-reference/#validate-your-webhook-endpoint

with a Javascript implementation here:

https://github.com/zoom/webhook-sample/blob/master/index.js#L24-L49

As far as I can tell, this would not be simple to accomplish with existing functionality.

@devorbitus
Copy link

However, this PR #1227 looks like it would make it easy to extract that header value that Zoom sends and then run the required calculations to return the validated expected value..

@Umang01-hash
Copy link
Contributor

@devorbitus Thank you for highlighting this use case! While the proposed PR (#1227) simplifies header access, webhook validation for Zoom can be effectively handled at the middleware level. Middleware has full access to headers, including x-zm-request-timestamp and x-zm-signature, as well as the request body, making it an ideal place to handle the validation logic.

By performing the signature verification in middleware, we can ensure that only validated requests proceed to the handler, streamlining the process.

@devorbitus
Copy link

@Umang01-hash
Can you show or direct me to a simplified example (even pseudocode would be helpful) of how someone can accomplish this?

The flow in the javascript code looks at the payload and when a specific validation property is detected it responds back a specific way instead of responding back with usual payload, how would a middleware implementation accomplish this?

@marvfinsy
Copy link
Author

marvfinsy commented Nov 21, 2024

In my case i'm redirecting to a vendor site. The vendor has a unique set of rules regarding authentication etc... Some information required for authentication i've saved in a badger key-value store. I also need an http client for vendor calls. Once authenticated i can redirect based on the current endpoint path.

I could write a handler for each endpoint that calls a "common handler" and pass the endpoint path as a string to the "common handler". But it would be easier to have the one "common handler" accessing the http.Request.URL.Path for the information.

  1. For the authentication part. Not sure about accessing an http client with error handling and doing database reads/saves from middleware...
  2. For getting the endpoint_path. Reading ur response i assume i have access to the http.Request.URL object in middleware. cool... But i cannot set data in the context (thinking of Gin) to use it in a handler (can i) ??? I guess i could add a "private" header in the middleware and read it in the handler. But that feels like a extreme "hack"... :o)

@Umang01-hash
Copy link
Contributor

@devorbitus Here is my attempt to provide you with some implementation regarding how can we achieve our use-case using a middleware:

package main

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net/http"

	"gofr.dev/pkg/gofr"
	gofrHTTP "gofr.dev/pkg/gofr/http"
)

func main() {
	// Create a new application
	a := gofr.New()

	// Register the middleware for validating Zoom webhooks
	a.UseMiddleware(zoomWebhookValidationMiddleware("webhook-secret"))

	// Register the POST handler for the Zoom webhook
	a.POST("/zoom-webhook", handler)

	// Run the application
	a.Run()
}

// handler processes the validated webhook request
func handler(c *gofr.Context) (interface{}, error) {
	// Check if the request was validated by the middleware
	validated, ok := c.Value("zoomValidated").(bool)
	if !ok || !validated {
		return nil, fmt.Errorf("invalid signature")
	}

	// Parse the request body as per user-defined structure
	var requestBody map[string]interface{} // Generic map for flexibility
	if err := c.Bind(&requestBody); err != nil {
		return nil, fmt.Errorf("invalid request body: %w", err)
	}

	// Return the validated request body as a response
	return map[string]interface{}{
		"validated": validated,
		"body":      requestBody,
	}, nil
}

// zoomWebhookValidationMiddleware validates Zoom webhook requests
func zoomWebhookValidationMiddleware(secret string) gofrHTTP.Middleware {
	return func(inner http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Extract required headers for validation
			timestamp := r.Header.Get("x-zm-request-timestamp")
			signature := r.Header.Get("x-zm-signature")

			// Validate that the necessary headers are present
			if timestamp == "" || signature == "" {
				http.Error(w, "Missing required headers", http.StatusBadRequest)
				return
			}

			// Read and reset the request body
			bodyBytes, err := io.ReadAll(r.Body)
			if err != nil {
				http.Error(w, "Failed to read request body", http.StatusInternalServerError)
				return
			}
			r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

			// Perform the signature validation
			if !validateZoomSignature(secret, timestamp, bodyBytes, signature) {
				http.Error(w, "Invalid signature", http.StatusUnauthorized)
				return
			}

			// Mark the request as validated and pass it to the next handler
			ctx := context.WithValue(r.Context(), "zoomValidated", true)
			inner.ServeHTTP(w, r.WithContext(ctx))
		})
	}
}

I have used context.WithValue to pass down any necessary information to handler in case we want to give a specific response based on request and else if it is not verified it can return from middleware itself.

Please let me know if we can provide any further assistance with respect to your use-case.

Thankyou.

@Umang01-hash
Copy link
Contributor

Umang01-hash commented Nov 22, 2024

@marvfinsy addressing your concerns:

GoFr supports adding data to the context within middleware, which can be accessed in handlers:
(I have shown an example of how to do this above please refer it.)

Also it's not generally suggested to use an HTTP client or database in middleware, we should perform essential tasks like authentication and pass results through the context.

@marvfinsy
Copy link
Author

marvfinsy commented Nov 22, 2024

thx...

Appears i can use middleware to get path from request.URL then save and read it from context... That works! Will give a "try" this weekend!

thx for help...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants