This commit is contained in:
134
internal/httpapp/auth_middleware.go
Normal file
134
internal/httpapp/auth_middleware.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package httpapp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"gitea.avt.data-center.id/othman.suseno/atlas/internal/auth"
|
||||
"gitea.avt.data-center.id/othman.suseno/atlas/internal/models"
|
||||
)
|
||||
|
||||
const (
|
||||
userCtxKey ctxKey = "user"
|
||||
roleCtxKey ctxKey = "role"
|
||||
)
|
||||
|
||||
// authMiddleware validates JWT tokens and extracts user info
|
||||
func (a *App) authMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Skip auth for public endpoints
|
||||
if a.isPublicEndpoint(r.URL.Path) {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract token from Authorization header
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "missing authorization header"})
|
||||
return
|
||||
}
|
||||
|
||||
// Parse "Bearer <token>"
|
||||
parts := strings.Split(authHeader, " ")
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid authorization header format"})
|
||||
return
|
||||
}
|
||||
|
||||
token := parts[1]
|
||||
claims, err := a.authService.ValidateToken(token)
|
||||
if err != nil {
|
||||
if err == auth.ErrExpiredToken {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "token expired"})
|
||||
} else {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid token"})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get user from store
|
||||
user, err := a.userStore.GetByID(claims.UserID)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "user not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if !user.Active {
|
||||
writeJSON(w, http.StatusForbidden, map[string]string{"error": "user account is disabled"})
|
||||
return
|
||||
}
|
||||
|
||||
// Add user info to context
|
||||
ctx := context.WithValue(r.Context(), userCtxKey, user)
|
||||
ctx = context.WithValue(ctx, roleCtxKey, user.Role)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
// requireRole middleware checks if user has required role
|
||||
func (a *App) requireRole(allowedRoles ...models.Role) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
role, ok := r.Context().Value(roleCtxKey).(models.Role)
|
||||
if !ok {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user role is in allowed roles
|
||||
allowed := false
|
||||
for _, allowedRole := range allowedRoles {
|
||||
if role == allowedRole {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
writeJSON(w, http.StatusForbidden, map[string]string{"error": "insufficient permissions"})
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// isPublicEndpoint checks if an endpoint is public (no auth required)
|
||||
func (a *App) isPublicEndpoint(path string) bool {
|
||||
publicPaths := []string{
|
||||
"/healthz",
|
||||
"/metrics",
|
||||
"/api/v1/auth/login",
|
||||
"/api/v1/auth/logout",
|
||||
"/", // Dashboard (can be made protected later)
|
||||
}
|
||||
|
||||
for _, publicPath := range publicPaths {
|
||||
if path == publicPath || strings.HasPrefix(path, publicPath+"/") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Static files are public
|
||||
if strings.HasPrefix(path, "/static/") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// getUserFromContext extracts user from request context
|
||||
func getUserFromContext(r *http.Request) (*models.User, bool) {
|
||||
user, ok := r.Context().Value(userCtxKey).(*models.User)
|
||||
return user, ok
|
||||
}
|
||||
|
||||
// getRoleFromContext extracts role from request context
|
||||
func getRoleFromContext(r *http.Request) (models.Role, bool) {
|
||||
role, ok := r.Context().Value(roleCtxKey).(models.Role)
|
||||
return role, ok
|
||||
}
|
||||
Reference in New Issue
Block a user