Open marvfinsy opened 6 days ago
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.
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.
@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!
@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.
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..
@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.
@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?
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.
@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.
@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.
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...
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:
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