add authentication method
Some checks failed
CI / test-build (push) Failing after 2m1s

This commit is contained in:
2025-12-14 23:55:12 +07:00
parent ed96137bad
commit 54e76d9304
18 changed files with 2197 additions and 34 deletions

View 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
}