From ebdd7bd74140d1f90fbbbf788cbe569b00049049 Mon Sep 17 00:00:00 2001 From: LordMathis Date: Sat, 23 Nov 2024 00:29:26 +0100 Subject: [PATCH] Implement auth package tests --- server/internal/api/routes.go | 6 +- server/internal/auth/middleware.go | 51 ++------ server/internal/auth/middleware_test.go | 112 ++++-------------- server/internal/context/context.go | 62 ++++++++++ .../context.go => context/middleware.go} | 23 ++-- server/internal/handlers/admin_handlers.go | 4 +- server/internal/handlers/auth_handlers.go | 4 +- server/internal/handlers/file_handlers.go | 16 +-- server/internal/handlers/git_handlers.go | 6 +- server/internal/handlers/user_handlers.go | 8 +- .../internal/handlers/workspace_handlers.go | 16 +-- server/internal/httpcontext/context.go | 31 ----- 12 files changed, 136 insertions(+), 203 deletions(-) create mode 100644 server/internal/context/context.go rename server/internal/{middleware/context.go => context/middleware.go} (59%) delete mode 100644 server/internal/httpcontext/context.go diff --git a/server/internal/api/routes.go b/server/internal/api/routes.go index 35751aa..1409ddc 100644 --- a/server/internal/api/routes.go +++ b/server/internal/api/routes.go @@ -3,9 +3,9 @@ package api import ( "novamd/internal/auth" + "novamd/internal/context" "novamd/internal/db" "novamd/internal/handlers" - "novamd/internal/middleware" "novamd/internal/storage" "github.com/go-chi/chi/v5" @@ -29,7 +29,7 @@ func SetupRoutes(r chi.Router, db db.Database, s storage.Manager, authMiddleware r.Group(func(r chi.Router) { // Apply authentication middleware to all routes in this group r.Use(authMiddleware.Authenticate) - r.Use(middleware.WithUserContext) + r.Use(context.WithUserContextMiddleware) // Auth routes r.Post("/auth/logout", handler.Logout(sessionService)) @@ -67,7 +67,7 @@ func SetupRoutes(r chi.Router, db db.Database, s storage.Manager, authMiddleware // Single workspace routes r.Route("/{workspaceName}", func(r chi.Router) { - r.Use(middleware.WithWorkspaceContext(db)) + r.Use(context.WithWorkspaceContextMiddleware(db)) r.Use(authMiddleware.RequireWorkspaceAccess) r.Get("/", handler.GetWorkspace()) diff --git a/server/internal/auth/middleware.go b/server/internal/auth/middleware.go index 864ebd3..e669460 100644 --- a/server/internal/auth/middleware.go +++ b/server/internal/auth/middleware.go @@ -1,25 +1,12 @@ package auth import ( - "context" - "fmt" "net/http" "strings" - "novamd/internal/httpcontext" + "novamd/internal/context" ) -type contextKey string - -// UserContextKey is the key used to store user claims in the request context -const UserContextKey contextKey = "user" - -// UserClaims represents the user information stored in the request context -type UserClaims struct { - UserID int - Role string -} - // Middleware handles JWT authentication for protected routes type Middleware struct { jwtManager JWTManager @@ -70,14 +57,14 @@ func (m *Middleware) Authenticate(next http.Handler) http.Handler { return } - // Add user claims to request context - ctx := context.WithValue(r.Context(), UserContextKey, UserClaims{ - UserID: claims.UserID, - Role: claims.Role, - }) + // Create handler context with user information + hctx := &context.HandlerContext{ + UserID: claims.UserID, + UserRole: claims.Role, + } - // Call the next handler with the updated context - next.ServeHTTP(w, r.WithContext(ctx)) + // Add context to request and continue + next.ServeHTTP(w, context.WithHandlerContext(r, hctx)) }) } @@ -89,13 +76,12 @@ func (m *Middleware) Authenticate(next http.Handler) http.Handler { func (m *Middleware) RequireRole(role string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - claims, ok := r.Context().Value(UserContextKey).(UserClaims) + ctx, ok := context.GetRequestContext(w, r) if !ok { - http.Error(w, "Unauthorized", http.StatusUnauthorized) return } - if claims.Role != role && claims.Role != "admin" { + if ctx.UserRole != role && ctx.UserRole != "admin" { http.Error(w, "Insufficient permissions", http.StatusForbidden) return } @@ -112,8 +98,7 @@ func (m *Middleware) RequireRole(role string) func(http.Handler) http.Handler { // - http.Handler: the handler function func (m *Middleware) RequireWorkspaceAccess(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Get our handler context - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -133,17 +118,3 @@ func (m *Middleware) RequireWorkspaceAccess(next http.Handler) http.Handler { next.ServeHTTP(w, r) }) } - -// GetUserFromContext retrieves user claims from the request context -// Parameters: -// - ctx: the request context -// Returns: -// - *UserClaims: the user claims -// - error: any error that occurred -func GetUserFromContext(ctx context.Context) (*UserClaims, error) { - claims, ok := ctx.Value(UserContextKey).(UserClaims) - if !ok { - return nil, fmt.Errorf("no user found in context") - } - return &claims, nil -} diff --git a/server/internal/auth/middleware_test.go b/server/internal/auth/middleware_test.go index 153bf33..71bdc9d 100644 --- a/server/internal/auth/middleware_test.go +++ b/server/internal/auth/middleware_test.go @@ -1,15 +1,13 @@ package auth_test import ( - "context" "net/http" "net/http/httptest" - "strings" "testing" "time" "novamd/internal/auth" - "novamd/internal/httpcontext" + "novamd/internal/context" "novamd/internal/models" ) @@ -97,7 +95,7 @@ func TestAuthenticateMiddleware(t *testing.T) { // Create test handler nextCalled := false - next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { nextCalled = true w.WriteHeader(http.StatusOK) }) @@ -158,12 +156,15 @@ func TestRequireRole(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - // Create context with user claims - ctx := context.WithValue(context.Background(), auth.UserContextKey, auth.UserClaims{ - UserID: 1, - Role: tc.userRole, - }) - req := httptest.NewRequest("GET", "/test", nil).WithContext(ctx) + // Create handler context with user info + hctx := &context.HandlerContext{ + UserID: 1, + UserRole: tc.userRole, + } + + // Create request with handler context + req := httptest.NewRequest("GET", "/test", nil) + req = context.WithHandlerContext(req, hctx) w := newMockResponseWriter() // Create test handler @@ -201,13 +202,13 @@ func TestRequireWorkspaceAccess(t *testing.T) { testCases := []struct { name string - setupContext func() *httpcontext.HandlerContext + setupContext func() *context.HandlerContext wantStatusCode int }{ { name: "workspace owner access", - setupContext: func() *httpcontext.HandlerContext { - return &httpcontext.HandlerContext{ + setupContext: func() *context.HandlerContext { + return &context.HandlerContext{ UserID: 1, UserRole: "editor", Workspace: &models.Workspace{ @@ -220,8 +221,8 @@ func TestRequireWorkspaceAccess(t *testing.T) { }, { name: "admin access to other's workspace", - setupContext: func() *httpcontext.HandlerContext { - return &httpcontext.HandlerContext{ + setupContext: func() *context.HandlerContext { + return &context.HandlerContext{ UserID: 2, UserRole: "admin", Workspace: &models.Workspace{ @@ -234,8 +235,8 @@ func TestRequireWorkspaceAccess(t *testing.T) { }, { name: "unauthorized access attempt", - setupContext: func() *httpcontext.HandlerContext { - return &httpcontext.HandlerContext{ + setupContext: func() *context.HandlerContext { + return &context.HandlerContext{ UserID: 2, UserRole: "editor", Workspace: &models.Workspace{ @@ -248,8 +249,8 @@ func TestRequireWorkspaceAccess(t *testing.T) { }, { name: "no workspace in context", - setupContext: func() *httpcontext.HandlerContext { - return &httpcontext.HandlerContext{ + setupContext: func() *context.HandlerContext { + return &context.HandlerContext{ UserID: 1, UserRole: "editor", Workspace: nil, @@ -262,8 +263,8 @@ func TestRequireWorkspaceAccess(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Create request with context - ctx := context.WithValue(context.Background(), httpcontext.HandlerContextKey, tc.setupContext()) - req := httptest.NewRequest("GET", "/test", nil).WithContext(ctx) + req := httptest.NewRequest("GET", "/test", nil) + req = context.WithHandlerContext(req, tc.setupContext()) w := newMockResponseWriter() // Create test handler @@ -291,72 +292,3 @@ func TestRequireWorkspaceAccess(t *testing.T) { }) } } - -func TestGetUserFromContext(t *testing.T) { - testCases := []struct { - name string - setupCtx func() context.Context - wantUserID int - wantRole string - wantErr bool - errContains string - }{ - { - name: "valid user context", - setupCtx: func() context.Context { - return context.WithValue(context.Background(), auth.UserContextKey, auth.UserClaims{ - UserID: 1, - Role: "admin", - }) - }, - wantUserID: 1, - wantRole: "admin", - wantErr: false, - }, - { - name: "missing user context", - setupCtx: func() context.Context { - return context.Background() - }, - wantErr: true, - errContains: "no user found in context", - }, - { - name: "invalid context value type", - setupCtx: func() context.Context { - return context.WithValue(context.Background(), auth.UserContextKey, "invalid") - }, - wantErr: true, - errContains: "no user found in context", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - ctx := tc.setupCtx() - claims, err := auth.GetUserFromContext(ctx) - - if tc.wantErr { - if err == nil { - t.Error("expected error, got nil") - } else if tc.errContains != "" && !strings.Contains(err.Error(), tc.errContains) { - t.Errorf("error = %v, want error containing %v", err, tc.errContains) - } - return - } - - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - if claims.UserID != tc.wantUserID { - t.Errorf("UserID = %v, want %v", claims.UserID, tc.wantUserID) - } - - if claims.Role != tc.wantRole { - t.Errorf("Role = %v, want %v", claims.Role, tc.wantRole) - } - }) - } -} diff --git a/server/internal/context/context.go b/server/internal/context/context.go new file mode 100644 index 0000000..dda7eeb --- /dev/null +++ b/server/internal/context/context.go @@ -0,0 +1,62 @@ +// Package context provides functions for managing request context +package context + +import ( + "context" + "fmt" + "net/http" + "novamd/internal/models" +) + +type contextKey string + +const ( + // HandlerContextKey is the key used to store handler context in the request context + HandlerContextKey contextKey = "handlerContext" +) + +// UserClaims represents user information from authentication +type UserClaims struct { + UserID int + Role string +} + +// HandlerContext holds the request-specific data available to all handlers +type HandlerContext struct { + UserID int + UserRole string + Workspace *models.Workspace // Optional, only set for workspace routes +} + +// GetRequestContext retrieves the handler context from the request +func GetRequestContext(w http.ResponseWriter, r *http.Request) (*HandlerContext, bool) { + ctx := r.Context().Value(HandlerContextKey) + if ctx == nil { + http.Error(w, "Internal server error", http.StatusInternalServerError) + return nil, false + } + return ctx.(*HandlerContext), true +} + +// WithHandlerContext adds handler context to the request +func WithHandlerContext(r *http.Request, hctx *HandlerContext) *http.Request { + return r.WithContext(context.WithValue(r.Context(), HandlerContextKey, hctx)) +} + +// GetUserFromContext retrieves user claims from the context +func GetUserFromContext(ctx context.Context) (*UserClaims, error) { + val := ctx.Value(HandlerContextKey) + if val == nil { + return nil, fmt.Errorf("no user found in context") + } + + hctx, ok := val.(*HandlerContext) + if !ok { + return nil, fmt.Errorf("invalid context type") + } + + return &UserClaims{ + UserID: hctx.UserID, + Role: hctx.UserRole, + }, nil +} diff --git a/server/internal/middleware/context.go b/server/internal/context/middleware.go similarity index 59% rename from server/internal/middleware/context.go rename to server/internal/context/middleware.go index 288ed24..9c1d9b3 100644 --- a/server/internal/middleware/context.go +++ b/server/internal/context/middleware.go @@ -1,38 +1,37 @@ -package middleware +package context import ( "net/http" - "novamd/internal/auth" "novamd/internal/db" - "novamd/internal/httpcontext" "github.com/go-chi/chi/v5" ) -// User ID and User Role context -func WithUserContext(next http.Handler) http.Handler { +// WithUserContextMiddleware extracts user information from JWT claims +// and adds it to the request context +func WithUserContextMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - claims, err := auth.GetUserFromContext(r.Context()) + claims, err := GetUserFromContext(r.Context()) if err != nil { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } - hctx := &httpcontext.HandlerContext{ + hctx := &HandlerContext{ UserID: claims.UserID, UserRole: claims.Role, } - r = httpcontext.WithHandlerContext(r, hctx) + r = WithHandlerContext(r, hctx) next.ServeHTTP(w, r) }) } -// Workspace context -func WithWorkspaceContext(db db.Database) func(http.Handler) http.Handler { +// WithWorkspaceContextMiddleware adds workspace information to the request context +func WithWorkspaceContextMiddleware(db db.Database) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := GetRequestContext(w, r) if !ok { return } @@ -46,7 +45,7 @@ func WithWorkspaceContext(db db.Database) func(http.Handler) http.Handler { // Update existing context with workspace ctx.Workspace = workspace - r = httpcontext.WithHandlerContext(r, ctx) + r = WithHandlerContext(r, ctx) next.ServeHTTP(w, r) }) } diff --git a/server/internal/handlers/admin_handlers.go b/server/internal/handlers/admin_handlers.go index 059c8cb..5ed8d42 100644 --- a/server/internal/handlers/admin_handlers.go +++ b/server/internal/handlers/admin_handlers.go @@ -3,8 +3,8 @@ package handlers import ( "encoding/json" "net/http" + "novamd/internal/context" "novamd/internal/db" - "novamd/internal/httpcontext" "novamd/internal/models" "novamd/internal/storage" "strconv" @@ -172,7 +172,7 @@ func (h *Handler) AdminUpdateUser() http.HandlerFunc { // AdminDeleteUser deletes a specific user func (h *Handler) AdminDeleteUser() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } diff --git a/server/internal/handlers/auth_handlers.go b/server/internal/handlers/auth_handlers.go index cce4c30..d6aec83 100644 --- a/server/internal/handlers/auth_handlers.go +++ b/server/internal/handlers/auth_handlers.go @@ -4,7 +4,7 @@ import ( "encoding/json" "net/http" "novamd/internal/auth" - "novamd/internal/httpcontext" + "novamd/internal/context" "novamd/internal/models" "golang.org/x/crypto/bcrypt" @@ -129,7 +129,7 @@ func (h *Handler) RefreshToken(authService *auth.SessionService) http.HandlerFun // GetCurrentUser returns the currently authenticated user func (h *Handler) GetCurrentUser() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } diff --git a/server/internal/handlers/file_handlers.go b/server/internal/handlers/file_handlers.go index d970fa1..9f75eb2 100644 --- a/server/internal/handlers/file_handlers.go +++ b/server/internal/handlers/file_handlers.go @@ -5,14 +5,14 @@ import ( "io" "net/http" - "novamd/internal/httpcontext" + "novamd/internal/context" "github.com/go-chi/chi/v5" ) func (h *Handler) ListFiles() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -29,7 +29,7 @@ func (h *Handler) ListFiles() http.HandlerFunc { func (h *Handler) LookupFileByName() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -52,7 +52,7 @@ func (h *Handler) LookupFileByName() http.HandlerFunc { func (h *Handler) GetFileContent() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -71,7 +71,7 @@ func (h *Handler) GetFileContent() http.HandlerFunc { func (h *Handler) SaveFile() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -95,7 +95,7 @@ func (h *Handler) SaveFile() http.HandlerFunc { func (h *Handler) DeleteFile() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -114,7 +114,7 @@ func (h *Handler) DeleteFile() http.HandlerFunc { func (h *Handler) GetLastOpenedFile() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -136,7 +136,7 @@ func (h *Handler) GetLastOpenedFile() http.HandlerFunc { func (h *Handler) UpdateLastOpenedFile() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } diff --git a/server/internal/handlers/git_handlers.go b/server/internal/handlers/git_handlers.go index f8ee589..ea6eeff 100644 --- a/server/internal/handlers/git_handlers.go +++ b/server/internal/handlers/git_handlers.go @@ -4,12 +4,12 @@ import ( "encoding/json" "net/http" - "novamd/internal/httpcontext" + "novamd/internal/context" ) func (h *Handler) StageCommitAndPush() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -40,7 +40,7 @@ func (h *Handler) StageCommitAndPush() http.HandlerFunc { func (h *Handler) PullChanges() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } diff --git a/server/internal/handlers/user_handlers.go b/server/internal/handlers/user_handlers.go index 013baf0..678c8e5 100644 --- a/server/internal/handlers/user_handlers.go +++ b/server/internal/handlers/user_handlers.go @@ -4,7 +4,7 @@ import ( "encoding/json" "net/http" - "novamd/internal/httpcontext" + "novamd/internal/context" "golang.org/x/crypto/bcrypt" ) @@ -22,7 +22,7 @@ type DeleteAccountRequest struct { func (h *Handler) GetUser() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -40,7 +40,7 @@ func (h *Handler) GetUser() http.HandlerFunc { // UpdateProfile updates the current user's profile func (h *Handler) UpdateProfile() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -144,7 +144,7 @@ func (h *Handler) UpdateProfile() http.HandlerFunc { // DeleteAccount handles user account deletion func (h *Handler) DeleteAccount() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } diff --git a/server/internal/handlers/workspace_handlers.go b/server/internal/handlers/workspace_handlers.go index 0f2a012..8dee442 100644 --- a/server/internal/handlers/workspace_handlers.go +++ b/server/internal/handlers/workspace_handlers.go @@ -5,13 +5,13 @@ import ( "fmt" "net/http" - "novamd/internal/httpcontext" + "novamd/internal/context" "novamd/internal/models" ) func (h *Handler) ListWorkspaces() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -28,7 +28,7 @@ func (h *Handler) ListWorkspaces() http.HandlerFunc { func (h *Handler) CreateWorkspace() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -56,7 +56,7 @@ func (h *Handler) CreateWorkspace() http.HandlerFunc { func (h *Handler) GetWorkspace() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -83,7 +83,7 @@ func gitSettingsChanged(new, old *models.Workspace) bool { func (h *Handler) UpdateWorkspace() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -134,7 +134,7 @@ func (h *Handler) UpdateWorkspace() http.HandlerFunc { func (h *Handler) DeleteWorkspace() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -197,7 +197,7 @@ func (h *Handler) DeleteWorkspace() http.HandlerFunc { func (h *Handler) GetLastWorkspaceName() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } @@ -214,7 +214,7 @@ func (h *Handler) GetLastWorkspaceName() http.HandlerFunc { func (h *Handler) UpdateLastWorkspaceName() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - ctx, ok := httpcontext.GetRequestContext(w, r) + ctx, ok := context.GetRequestContext(w, r) if !ok { return } diff --git a/server/internal/httpcontext/context.go b/server/internal/httpcontext/context.go deleted file mode 100644 index 1e9b278..0000000 --- a/server/internal/httpcontext/context.go +++ /dev/null @@ -1,31 +0,0 @@ -package httpcontext - -import ( - "context" - "net/http" - "novamd/internal/models" -) - -// HandlerContext holds the request-specific data available to all handlers -type HandlerContext struct { - UserID int - UserRole string - Workspace *models.Workspace -} - -type contextKey string - -const HandlerContextKey contextKey = "handlerContext" - -func GetRequestContext(w http.ResponseWriter, r *http.Request) (*HandlerContext, bool) { - ctx := r.Context().Value(HandlerContextKey) - if ctx == nil { - http.Error(w, "Internal server error", http.StatusInternalServerError) - return nil, false - } - return ctx.(*HandlerContext), true -} - -func WithHandlerContext(r *http.Request, hctx *HandlerContext) *http.Request { - return r.WithContext(context.WithValue(r.Context(), HandlerContextKey, hctx)) -}