add: login & token validation tapi belum connect ke DB
This commit is contained in:
194
internal/api/middleware/auth.go
Normal file
194
internal/api/middleware/auth.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/service"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
UserIDKey contextKey = "user_id"
|
||||
UserRoleKey contextKey = "user_role"
|
||||
UserEmailKey contextKey = "user_email"
|
||||
)
|
||||
|
||||
// WhitelistedEndpoints contains paths that can be accessed without authentication
|
||||
var WhitelistedEndpoints = []*regexp.Regexp{
|
||||
// Study by UID
|
||||
regexp.MustCompile(`^/dicomWeb/studies\?.*StudyInstanceUID=.+`),
|
||||
|
||||
// Frame endpoint
|
||||
regexp.MustCompile(`^/dicomWeb/studies/[^/]+/series/[^/]+/instances/[^/]+/frames/\d+$`),
|
||||
}
|
||||
|
||||
// Auth middleware authenticates requests using JWT tokens
|
||||
func Auth(authService *service.AuthService, logger *zap.Logger) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Check if the request path is whitelisted
|
||||
path := r.URL.Path
|
||||
if r.URL.RawQuery != "" {
|
||||
path = path + "?" + r.URL.RawQuery
|
||||
}
|
||||
|
||||
for _, pattern := range WhitelistedEndpoints {
|
||||
if pattern.MatchString(path) {
|
||||
// Path is whitelisted, skip authentication
|
||||
logger.Debug("Skipping authentication for whitelisted path", zap.String("path", path))
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get authorization header
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
logger.Warn("Missing Authorization header")
|
||||
respondWithError(w, http.StatusUnauthorized, "missing authorization header")
|
||||
return
|
||||
}
|
||||
|
||||
// Extract token from Bearer token
|
||||
bearerToken := strings.Split(authHeader, " ")
|
||||
if len(bearerToken) != 2 || strings.ToLower(bearerToken[0]) != "bearer" {
|
||||
logger.Warn("Invalid Authorization header format")
|
||||
respondWithError(w, http.StatusUnauthorized, "invalid authorization format")
|
||||
return
|
||||
}
|
||||
|
||||
token := bearerToken[1]
|
||||
|
||||
// Validate token
|
||||
claims, err := authService.ValidateToken(token)
|
||||
if err != nil {
|
||||
logger.Warn("Invalid or expired token", zap.Error(err))
|
||||
respondWithError(w, http.StatusUnauthorized, "invalid or expired token")
|
||||
return
|
||||
}
|
||||
|
||||
// Check token type
|
||||
if claims.TokenType != "access" {
|
||||
logger.Warn("Invalid token type", zap.String("tokenType", claims.TokenType))
|
||||
respondWithError(w, http.StatusUnauthorized, "invalid token type")
|
||||
return
|
||||
}
|
||||
|
||||
// Add user info to request context
|
||||
ctx := context.WithValue(r.Context(), UserIDKey, claims.UserID)
|
||||
ctx = context.WithValue(ctx, UserRoleKey, claims.Role)
|
||||
ctx = context.WithValue(ctx, UserEmailKey, claims.Email)
|
||||
|
||||
// Log user info
|
||||
logger.Info("Authenticated user", zap.String("userID", claims.UserID), zap.String("role", claims.Role), zap.String("email", claims.Email))
|
||||
|
||||
// Continue with the request
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// RoleRequired middleware checks if user has the required role
|
||||
func RoleRequired(roles ...string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Check if the request path is whitelisted first
|
||||
path := r.URL.Path
|
||||
if r.URL.RawQuery != "" {
|
||||
path = path + "?" + r.URL.RawQuery
|
||||
}
|
||||
|
||||
for _, pattern := range WhitelistedEndpoints {
|
||||
if pattern.MatchString(path) {
|
||||
// Path is whitelisted, skip role check
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get user role from context
|
||||
userRole, ok := r.Context().Value(UserRoleKey).(string)
|
||||
if !ok {
|
||||
respondWithError(w, http.StatusUnauthorized, "user context not found")
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user has one of the required roles
|
||||
hasRole := false
|
||||
for _, role := range roles {
|
||||
if userRole == role {
|
||||
hasRole = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasRole {
|
||||
respondWithError(w, http.StatusForbidden, "insufficient permissions")
|
||||
return
|
||||
}
|
||||
|
||||
// Continue with the request
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// PatientViewRestriction middleware restricts patients to view only their studies
|
||||
func PatientViewRestriction(logger *zap.Logger) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Check if the request path is whitelisted first
|
||||
path := r.URL.Path
|
||||
if r.URL.RawQuery != "" {
|
||||
path = path + "?" + r.URL.RawQuery
|
||||
}
|
||||
|
||||
for _, pattern := range WhitelistedEndpoints {
|
||||
if pattern.MatchString(path) {
|
||||
// Path is whitelisted, skip restrictions
|
||||
logger.Debug("Skipping patient restrictions for whitelisted path", zap.String("path", path))
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get user role from context
|
||||
userRole, ok := r.Context().Value(UserRoleKey).(string)
|
||||
if !ok {
|
||||
respondWithError(w, http.StatusUnauthorized, "user context not found")
|
||||
return
|
||||
}
|
||||
|
||||
// Only apply restrictions to patients
|
||||
if userRole != "patient" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Logic to restrict patients to only access their assigned studies
|
||||
// For now, we're just letting the request through, but in a real
|
||||
// implementation, you would check the study ID against the patient's
|
||||
// assigned studies.
|
||||
|
||||
// TODO: Check if the requested study is assigned to the patient
|
||||
// This would likely involve parsing the URL path to extract study ID
|
||||
// and checking it against a database of patient assignments
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to respond with JSON error
|
||||
func respondWithError(w http.ResponseWriter, code int, message string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
json.NewEncoder(w).Encode(map[string]string{"error": message})
|
||||
}
|
||||
Reference in New Issue
Block a user