Setup api auth middleware

This commit is contained in:
2024-11-01 17:04:24 +01:00
parent 46eeb18a31
commit 72680abdf4
2 changed files with 94 additions and 8 deletions

View File

@@ -4,7 +4,6 @@ import (
"novamd/internal/auth" "novamd/internal/auth"
"novamd/internal/db" "novamd/internal/db"
"novamd/internal/filesystem" "novamd/internal/filesystem"
"novamd/internal/models"
"github.com/go-chi/chi/v5" "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.Post("/auth/logout", Logout(sessionService))
r.Get("/auth/me", GetCurrentUser(db)) 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.Route("/users/{userId}", func(r chi.Router) {
r.Use(authMiddleware.RequireResourceOwnership)
r.Get("/", GetUser(db)) r.Get("/", GetUser(db))
// Workspace routes // Workspace routes
@@ -37,6 +48,9 @@ func SetupRoutes(r chi.Router, db *db.DB, fs *filesystem.FileSystem, authMiddlew
r.Put("/last", UpdateLastWorkspace(db)) r.Put("/last", UpdateLastWorkspace(db))
r.Route("/{workspaceId}", func(r chi.Router) { r.Route("/{workspaceId}", func(r chi.Router) {
// Add workspace ownership check
r.Use(authMiddleware.RequireWorkspaceOwnership(db))
r.Get("/", GetWorkspace(db)) r.Get("/", GetWorkspace(db))
r.Put("/", UpdateWorkspace(db, fs)) r.Put("/", UpdateWorkspace(db, fs))
r.Delete("/", DeleteWorkspace(db)) 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
})
}) })
} }

View File

@@ -4,7 +4,11 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"novamd/internal/db"
"strconv"
"strings" "strings"
"github.com/go-chi/chi/v5"
) )
type contextKey string 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 // GetUserFromContext retrieves user claims from the request context
func GetUserFromContext(ctx context.Context) (*UserClaims, error) { func GetUserFromContext(ctx context.Context) (*UserClaims, error) {
claims, ok := ctx.Value(UserContextKey).(UserClaims) claims, ok := ctx.Value(UserContextKey).(UserClaims)