From a32bdef0922560c98980144e412f3c41c49bd8ab Mon Sep 17 00:00:00 2001 From: ae Date: Tue, 1 Apr 2025 18:48:32 +0300 Subject: [PATCH] feat: notectx middleware --- server/pkg/service/middleware.go | 47 +++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/server/pkg/service/middleware.go b/server/pkg/service/middleware.go index b730a8b..fd85d5c 100644 --- a/server/pkg/service/middleware.go +++ b/server/pkg/service/middleware.go @@ -6,6 +6,7 @@ import ( "net/http" "time" + "git.umbrella.haus/ae/notatest/pkg/data" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/golang-jwt/jwt/v5" @@ -18,8 +19,6 @@ const ( defaultLogMsg = "incoming request" ) -type userCtxKey struct{} - // Get JWT bearer from request's authorization header, parse it with custom user claims, and // ensure its validity before attaching the claims to the request's context. func authMiddleware(jwtSecret string, expectedType string) func(http.Handler) http.Handler { @@ -30,7 +29,7 @@ func authMiddleware(jwtSecret string, expectedType string) func(http.Handler) ht respondError(w, http.StatusUnauthorized, fmt.Sprintf("Unauthorized: %s", err)) } - token, err := jwt.ParseWithClaims(tokenString, &userClaims{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenString, &userClaims{}, func(token *jwt.Token) (any, error) { return []byte(jwtSecret), nil }) if err != nil || !token.Valid { @@ -109,6 +108,46 @@ func userCtx(store UserStore) func(http.Handler) http.Handler { } } +// Append note data into request's context based on note ID as a URL parameter and user ID as +// context parameter. +func noteCtx(store NoteStore) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + noteIDStr := chi.URLParam(r, "id") + noteID, err := uuid.Parse(noteIDStr) + if err != nil { + respondError(w, http.StatusNotFound, "Invalid note ID") + return + } + + // NOTE: user must already be in the context (e.g. via JWT middleware) + user, ok := r.Context().Value(userCtxKey{}).(*userClaims) + if !ok { + respondError(w, http.StatusUnauthorized, "Unauthorized") + return + } + + userID, err := uuid.Parse(user.Subject) + if err != nil { + respondError(w, http.StatusInternalServerError, "Invalid user ID") + return + } + + note, err := store.GetNote(r.Context(), data.GetNoteParams{ + ID: noteID, + UserID: userID, + }) + if err != nil { + respondError(w, http.StatusNotFound, "Note not found") + return + } + + ctx := context.WithValue(r.Context(), noteCtxKey{}, note) + next.ServeHTTP(w, r.WithContext(ctx)) + }) + } +} + // Zerolog compatible logger middleware. Automatically logs and recovers from errors with HTTP 500 // response, by default logs to INFO level. func loggerMiddleware(log *zerolog.Logger) func(http.Handler) http.Handler { @@ -136,7 +175,7 @@ func loggerMiddleware(log *zerolog.Logger) func(http.Handler) http.Handler { log.Info(). Str("type", "access"). Timestamp(). - Fields(map[string]interface{}{ + Fields(map[string]any{ "remote_ip": r.RemoteAddr, "url": r.URL.Path, "proto": r.Proto,