package main import ( "context" "encoding/json" "fmt" "io" "libshared" "log" "net/http" "strings" "github.com/golang-jwt/jwt/v5" ) // Custom claims struct type MyClaims struct { Sub string `json:"sub"` Purpose string `json:"purpose"` Account string `json:"account"` jwt.RegisteredClaims } type PolicyRequest struct { Name string `json:"name"` Description string `json:"description"` PolicyDocument Policy `json:"policydocument"` } type PolicyResponse struct { // PolicyID string `json:"policy_id"` } func CreatePolicy(w http.ResponseWriter, r *http.Request) { log.Println("Create Policy Request") w.Header().Set("Content-Type", "application/json") // Only allow POST method if r.Method != http.MethodPost { log.Println("Invalid method:", r.Method) //w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusMethodNotAllowed) apiresponse := libshared.NewAPIResponse[PolicyResponse]("fail", "Method not allowed", PolicyResponse{}) json.NewEncoder(w).Encode(apiresponse) return } // Optional: enforce content type if r.Header.Get("Content-Type") != "application/json" { log.Println("Invalid Content-Type:", r.Header.Get("Content-Type")) //w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnsupportedMediaType) apiresponse := libshared.NewAPIResponse[PolicyResponse]("fail", "Content-Type must be application/json", PolicyResponse{}) json.NewEncoder(w).Encode(apiresponse) //http.Error(w, "Content-Type must be application/json", http.StatusUnsupportedMediaType) return } // Get JWT from Authorization header authHeader := r.Header.Get("Authorization") if authHeader == "" { log.Println("Missing Authorization header") //w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) apiresponse := libshared.NewAPIResponse[PolicyResponse]("fail", "Missing Authorization header", PolicyResponse{}) json.NewEncoder(w).Encode(apiresponse) //http.Error(w, "Missing Authorization header", http.StatusUnauthorized) return } parts := strings.SplitN(authHeader, " ", 2) if len(parts) != 2 || parts[0] != "Bearer" { log.Println("Invalid Authorization header format") //w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) apiresponse := libshared.NewAPIResponse[PolicyResponse]("fail", "Invalid Authorization format", PolicyResponse{}) json.NewEncoder(w).Encode(apiresponse) //http.Error(w, "Invalid Authorization format", http.StatusUnauthorized) return } tokenString := parts[1] // Replace with your actual secret or keyfunc secret := []byte("super-secret-key") claims := &MyClaims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { // Validate signing method if needed return secret, nil }) if err != nil || !token.Valid { log.Println("Invalid token:", err) //http.Error(w, "Invalid token", http.StatusUnauthorized) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) apiresponse := libshared.NewAPIResponse[PolicyResponse]("fail", "Invalid token", PolicyResponse{}) json.NewEncoder(w).Encode(apiresponse) return } // Access parsed values log.Println("sub:", claims.Sub) log.Println("purpose:", claims.Purpose) log.Println("account:", claims.Account) // exp and iat come from RegisteredClaims log.Println("exp:", claims.ExpiresAt) log.Println("iat:", claims.IssuedAt) // Read body with size limit (protect against huge requests) const maxBodyBytes = 1 << 20 // 1 MB r.Body = http.MaxBytesReader(w, r.Body, maxBodyBytes) // Decode JSON into our struct var policydocumentrequest PolicyRequest decoder := json.NewDecoder(r.Body) decoder.DisallowUnknownFields() // strict mode - reject unknown fields if err := decoder.Decode(&policydocumentrequest); err != nil { switch { case err == io.EOF: log.Println("Empty request body") w.WriteHeader(http.StatusBadRequest) apiresponse := libshared.NewAPIResponse[PolicyResponse]("fail", "Empty request body", PolicyResponse{}) json.NewEncoder(w).Encode(apiresponse) //http.Error(w, "Empty request body", http.StatusBadRequest) return case err.Error() == "http: request body too large": log.Println("Request body too large") w.WriteHeader(http.StatusRequestEntityTooLarge) apiresponse := libshared.NewAPIResponse[PolicyResponse]("fail", "Request body too large (max 1MB)", PolicyResponse{}) json.NewEncoder(w).Encode(apiresponse) //http.Error(w, "Request body too large (max 1MB)", http.StatusRequestEntityTooLarge) return default: log.Println("Invalid JSON:", err) w.WriteHeader(http.StatusBadRequest) apiresponse := libshared.NewAPIResponse[PolicyResponse]("fail", fmt.Sprintf("Invalid JSON: %v", err), PolicyResponse{}) json.NewEncoder(w).Encode(apiresponse) //http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest) return } } checkExisting := libshared.Pool.QueryRow( context.Background(), `SELECT FROM policies WHERE name=$1 AND accountid=$2`, policydocumentrequest.Name, claims.Account, ) err = checkExisting.Scan() if err == nil { log.Println("Policy with this name already exists for this account") w.WriteHeader(http.StatusConflict) apiresponse := libshared.NewAPIResponse[PolicyResponse]("fail", "Policy with this name already exists", PolicyResponse{}) json.NewEncoder(w).Encode(apiresponse) return } _, err = libshared.Pool.Exec( context.Background(), `INSERT INTO policies (name, accountid, description, document) VALUES ($1, $2, $3, $4)`, policydocumentrequest.Name, claims.Account, policydocumentrequest.Description, policydocumentrequest.PolicyDocument, ) if err != nil { log.Println("Database error:", err) w.WriteHeader(http.StatusInternalServerError) apiresponse := libshared.NewAPIResponse[PolicyResponse]("fail", "Internal server error", PolicyResponse{}) json.NewEncoder(w).Encode(apiresponse) return } // Success response w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) apiresponse := libshared.NewAPIResponse[PolicyResponse]("success", "Policy created", PolicyResponse{}) json.NewEncoder(w).Encode(apiresponse) // arnservice:region:account-id:resource-type:resource-id //pri := "pri:iam::" + accountid + ":policy/" + policydocumentrequest.PolicyName // response := map[string]interface{}{ // "status": "success", // //"pri": pri, // "message": "Policy created", // "timestamp": fmt.Sprintf("%d", time.Now().Unix()), // } return }