add: cors handler route and readme

This commit is contained in:
mario
2025-05-09 10:13:16 +07:00
parent 6c9ab574ce
commit 13bb380f51
13 changed files with 674 additions and 203 deletions

View File

@@ -3,12 +3,13 @@ package middleware
import (
"context"
"encoding/json"
"fmt"
"net/http"
"regexp"
"strings"
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/service"
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/auth"
"go.uber.org/zap"
)
@@ -18,6 +19,7 @@ const (
UserIDKey contextKey = "user_id"
UserRoleKey contextKey = "user_role"
UserEmailKey contextKey = "user_email"
ClaimsKey contextKey = "auth_claims" // Use this same key everywhere
)
// WhitelistedEndpoints contains paths that can be accessed without authentication
@@ -33,25 +35,10 @@ var WhitelistedEndpoints = []*regexp.Regexp{
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")
logger.Warn("Missing Authorization header", zap.String("path", r.URL.Path))
respondWithError(w, http.StatusUnauthorized, "missing authorization header")
return
}
@@ -59,7 +46,7 @@ func Auth(authService *service.AuthService, logger *zap.Logger) func(http.Handle
// Extract token from Bearer token
bearerToken := strings.Split(authHeader, " ")
if len(bearerToken) != 2 || strings.ToLower(bearerToken[0]) != "bearer" {
logger.Warn("Invalid Authorization header format")
logger.Warn("Invalid Authorization header format", zap.String("header", authHeader))
respondWithError(w, http.StatusUnauthorized, "invalid authorization format")
return
}
@@ -86,8 +73,13 @@ func Auth(authService *service.AuthService, logger *zap.Logger) func(http.Handle
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))
// Store the claims with the defined context key
ctx = context.WithValue(ctx, ClaimsKey, claims)
// Log successful authentication
logger.Debug("Auth middleware: Token validated",
zap.String("userID", claims.UserID),
zap.String("role", claims.Role))
// Continue with the request
next.ServeHTTP(w, r.WithContext(ctx))
@@ -140,55 +132,87 @@ func RoleRequired(roles ...string) func(http.Handler) http.Handler {
}
}
// PatientViewRestriction middleware restricts patients to view only their studies
// PatientViewRestriction ensures patients can only access their own 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")
// Get claims from context using the defined key
claimsValue := r.Context().Value(ClaimsKey)
if claimsValue == nil {
logger.Error("Missing claims in context - PatientViewRestriction middleware",
zap.String("path", r.URL.Path),
zap.String("method", r.Method))
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Only apply restrictions to patients
if userRole != "patient" {
claims, ok := claimsValue.(*auth.CustomClaims)
if !ok {
logger.Error("Invalid claims type in context",
zap.String("type", fmt.Sprintf("%T", claimsValue)))
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
logger.Debug("PatientViewRestriction: Got claims from context",
zap.String("userID", claims.UserID),
zap.String("role", claims.Role))
// Only apply restrictions to patient role
if claims.Role != "patient" {
// For non-patient roles, continue with the request
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.
// Parse the path to extract StudyInstanceUID if present
path := r.URL.Path
parts := strings.Split(path, "/")
// 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
// Check if this is a study-specific request
var requestedStudyUID string
for i, part := range parts {
if part == "studies" && i+1 < len(parts) {
requestedStudyUID = parts[i+1]
break
}
}
// If there's no study UID in the path, check query parameters
if requestedStudyUID == "" {
queryStudyUID := r.URL.Query().Get("StudyInstanceUID")
if queryStudyUID != "" {
requestedStudyUID = queryStudyUID
}
}
// If a study is being requested, verify patient has access
if requestedStudyUID != "" && requestedStudyUID != claims.StudyIUID {
logger.Warn("Patient attempted to access unauthorized study",
zap.String("userID", claims.UserID),
zap.String("role", claims.Role),
zap.String("authorizedStudy", claims.StudyIUID),
zap.String("requestedStudy", requestedStudyUID))
// Return 403 Forbidden with a clear message
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusForbidden)
json.NewEncoder(w).Encode(map[string]string{
"error": "Access denied: You do not have permission to view this study",
"code": "forbidden_study_access",
})
return
}
// Patient has access or is requesting a list (which will be filtered)
next.ServeHTTP(w, r)
})
}
}
// Helper function to respond with JSON error
func respondWithError(w http.ResponseWriter, code int, message string) {
// Helper function to respond with an error
func respondWithError(w http.ResponseWriter, statusCode int, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(map[string]string{"error": message})
}