Add admin-only CRUD endpoints for user management under /api/v1/system/users with full frontend implementation including create, edit, change password, and delete operations with self-protection guards. - Extend AuthDomain interface with ListUsers, GetUser, UpdateUser, UpdateUserPassword, DeleteUser methods - Add List, Update, Delete methods to UserStore with shared scanUser helper - Create UserHandler with 5 endpoints protected by admin middleware - Complete Users.vue admin page with modals and error handling - Make CreateUser accept role parameter for atomic user creation - Fix auth middleware to use database role as source of truth instead of stale JWT claims, preventing privilege persistence after demotion - Fix pre-existing gofmt issues in yt_dlp.go and config.go
120 lines
3.9 KiB
Go
120 lines
3.9 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"git.nakama.town/fmartingr/hako/internal/auth"
|
|
"git.nakama.town/fmartingr/hako/internal/auth/store"
|
|
"git.nakama.town/fmartingr/hako/internal/server/webcontext"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// AuthMiddleware is a global middleware that validates JWT tokens if present
|
|
// and adds user info to context. It does not block requests if no token is provided.
|
|
// This allows routes to optionally use authentication information.
|
|
// The middleware also verifies that the user exists in the database to prevent
|
|
// authentication with tokens for deleted users.
|
|
func AuthMiddleware(jwtService *auth.JWTService, userStore *store.UserStore) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Create webcontext for this request
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
var tokenString string
|
|
|
|
// Extract token from Authorization header if present
|
|
authHeader := r.Header.Get("Authorization")
|
|
if authHeader != "" {
|
|
// Check Bearer prefix
|
|
parts := strings.Split(authHeader, " ")
|
|
if len(parts) == 2 && parts[0] == "Bearer" {
|
|
tokenString = parts[1]
|
|
}
|
|
}
|
|
|
|
// Fallback: extract token from query parameter if not in header
|
|
// This allows embedding authenticated URLs (e.g., for images)
|
|
if tokenString == "" {
|
|
tokenString = r.URL.Query().Get("token")
|
|
}
|
|
|
|
// Validate token if present
|
|
if tokenString != "" {
|
|
// Validate token - if valid, verify user exists; if invalid, continue without context
|
|
claims, err := jwtService.ValidateToken(tokenString)
|
|
if err == nil {
|
|
// Token is valid, verify user exists in database
|
|
userID, parseErr := uuid.Parse(claims.UserID)
|
|
if parseErr == nil {
|
|
user, userErr := userStore.FindByID(userID)
|
|
if userErr == nil && user != nil {
|
|
// User exists, add user info to context
|
|
// Use role from database (source of truth)
|
|
role := user.Role
|
|
if role == "" {
|
|
role = "user"
|
|
}
|
|
c.SetUserWithRole(claims.UserID, claims.Email, role)
|
|
// Update request with new context
|
|
r = c.Request()
|
|
}
|
|
// If user doesn't exist, continue without setting context (don't block)
|
|
}
|
|
}
|
|
// If token is invalid, continue without setting context (don't block)
|
|
}
|
|
|
|
// Continue with request (with or without user context)
|
|
// Use the updated request if context was modified
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// RequireAuthMiddleware is a middleware that requires authentication.
|
|
// It checks if user info is in context and returns 401 if not authenticated.
|
|
func RequireAuthMiddleware() func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Create webcontext for this request
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Check if user is authenticated
|
|
if !c.UserIsLogged() {
|
|
http.Error(w, "Authorization required", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// User is authenticated, continue
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// RequireAdminMiddleware is a middleware that requires admin role.
|
|
// It checks if user is authenticated and has admin role, returns 403 if not admin.
|
|
func RequireAdminMiddleware() func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Create webcontext for this request
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Check if user is authenticated
|
|
if !c.UserIsLogged() {
|
|
http.Error(w, "Authorization required", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Check if user has admin role
|
|
role := c.GetUserRole()
|
|
if role != "admin" {
|
|
http.Error(w, "Admin access required", http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
// User is authenticated and is admin, continue
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|