From 72680abdf423882541441e08bd0629d841afbcec Mon Sep 17 00:00:00 2001 From: LordMathis Date: Fri, 1 Nov 2024 17:04:24 +0100 Subject: [PATCH] Setup api auth middleware --- backend/internal/api/routes.go | 24 ++++++--- backend/internal/auth/middleware.go | 78 +++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 8 deletions(-) diff --git a/backend/internal/api/routes.go b/backend/internal/api/routes.go index 62231b4..fba0260 100644 --- a/backend/internal/api/routes.go +++ b/backend/internal/api/routes.go @@ -4,7 +4,6 @@ import ( "novamd/internal/auth" "novamd/internal/db" "novamd/internal/filesystem" - "novamd/internal/models" "github.com/go-chi/chi/v5" ) @@ -25,8 +24,20 @@ func SetupRoutes(r chi.Router, db *db.DB, fs *filesystem.FileSystem, authMiddlew r.Post("/auth/logout", Logout(sessionService)) r.Get("/auth/me", GetCurrentUser(db)) - // User routes + // Admin-only routes + r.Group(func(r chi.Router) { + r.Use(authMiddleware.RequireRole("admin")) + + // TODO: Implement + // r.Get("/admin/users", ListUsers(db)) + // r.Post("/admin/users", CreateUser(db)) + // r.Delete("/admin/users/{userId}", DeleteUser(db)) + }) + + // User routes - protected by resource ownership r.Route("/users/{userId}", func(r chi.Router) { + r.Use(authMiddleware.RequireResourceOwnership) + r.Get("/", GetUser(db)) // Workspace routes @@ -37,6 +48,9 @@ func SetupRoutes(r chi.Router, db *db.DB, fs *filesystem.FileSystem, authMiddlew r.Put("/last", UpdateLastWorkspace(db)) r.Route("/{workspaceId}", func(r chi.Router) { + // Add workspace ownership check + r.Use(authMiddleware.RequireWorkspaceOwnership(db)) + r.Get("/", GetWorkspace(db)) r.Put("/", UpdateWorkspace(db, fs)) r.Delete("/", DeleteWorkspace(db)) @@ -61,11 +75,5 @@ func SetupRoutes(r chi.Router, db *db.DB, fs *filesystem.FileSystem, authMiddlew }) }) }) - - // Admin-only routes - r.Group(func(r chi.Router) { - r.Use(authMiddleware.RequireRole(string(models.RoleAdmin))) - // Admin-only endpoints - }) }) } diff --git a/backend/internal/auth/middleware.go b/backend/internal/auth/middleware.go index 3bac176..b73ed4e 100644 --- a/backend/internal/auth/middleware.go +++ b/backend/internal/auth/middleware.go @@ -4,7 +4,11 @@ import ( "context" "fmt" "net/http" + "novamd/internal/db" + "strconv" "strings" + + "github.com/go-chi/chi/v5" ) type contextKey string @@ -92,6 +96,80 @@ func (m *Middleware) RequireRole(role string) func(http.Handler) http.Handler { } } +// RequireResourceOwnership ensures users can only access their own resources +func (m *Middleware) RequireResourceOwnership(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Get requesting user from context (set by auth middleware) + claims, err := GetUserFromContext(r.Context()) + if err != nil { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + // Get requested user ID from URL + userIDStr := chi.URLParam(r, "userId") + requestedUserID, err := strconv.Atoi(userIDStr) + if err != nil { + http.Error(w, "Invalid user ID", http.StatusBadRequest) + return + } + + // Allow if user is accessing their own resources or is an admin + if claims.UserID != requestedUserID && claims.Role != "admin" { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + + next.ServeHTTP(w, r) + }) +} + +// RequireWorkspaceOwnership ensures users can only access workspaces they own +type WorkspaceGetter interface { + GetWorkspaceByID(id int) (*Workspace, error) +} + +type Workspace struct { + ID int + UserID int +} + +// RequireWorkspaceOwnership ensures users can only access workspaces they own +func (m *Middleware) RequireWorkspaceOwnership(db *db.DB) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Get requesting user from context + claims, err := GetUserFromContext(r.Context()) + if err != nil { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + // Get workspace ID from URL + workspaceID, err := strconv.Atoi(chi.URLParam(r, "workspaceId")) + if err != nil { + http.Error(w, "Invalid workspace ID", http.StatusBadRequest) + return + } + + // Get workspace from database + workspace, err := db.GetWorkspaceByID(workspaceID) + if err != nil { + http.Error(w, "Workspace not found", http.StatusNotFound) + return + } + + // Check if user owns the workspace or is admin + if workspace.UserID != claims.UserID && claims.Role != "admin" { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + + next.ServeHTTP(w, r) + }) + } +} + // GetUserFromContext retrieves user claims from the request context func GetUserFromContext(ctx context.Context) (*UserClaims, error) { claims, ok := ctx.Value(UserContextKey).(UserClaims)