feat(shared-sqs): remove auth from UI, add public /ui/api/ routes (v0.1.5)

- Public routes /ui/api/* without bearer token for demo
- UI loads dashboard immediately, no login required
- Admin API /admin/* still requires bearer token
- For demo purposes only — auth will be restored later
This commit is contained in:
“Naeel”
2026-04-09 18:23:17 +03:00
parent 12b3bb9bf3
commit 70c276b4e7
3 changed files with 127 additions and 165 deletions
+101 -98
View File
@@ -4,139 +4,142 @@
package router
import (
"encoding/json"
"encoding/xml"
"fmt"
"io"
"net/http"
"strings"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"net/http"
"strings"
"shared-sqs/app/admin"
"shared-sqs/app/auth"
"shared-sqs/app/interfaces"
sqs "shared-sqs/app/gosqs"
"shared-sqs/app/tenant"
"shared-sqs/app/ui"
"shared-sqs/app/admin"
"shared-sqs/app/auth"
sqs "shared-sqs/app/gosqs"
"shared-sqs/app/interfaces"
"shared-sqs/app/tenant"
"shared-sqs/app/ui"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
)
// New — создаёт HTTP router с tenant auth и admin API
func New(tenantStore *tenant.TenantStore, adminToken string) http.Handler {
r := mux.NewRouter()
r := mux.NewRouter()
// /health — публичный, без auth
r.HandleFunc("/health", health).Methods("GET")
// /health — публичный, без auth
r.HandleFunc("/health", health).Methods("GET")
// Admin API — Bearer token auth, регистрируется через AdminHandler
adminHandler := admin.NewHandler(tenantStore, adminToken)
adminHandler.RegisterRoutes(r)
// Admin API — Bearer token auth, регистрируется через AdminHandler
adminHandler := admin.NewHandler(tenantStore, adminToken)
adminHandler.RegisterRoutes(r)
// UI console — встроенный SPA, публичный доступ
r.PathPrefix("/ui").Handler(http.StripPrefix("/ui", ui.Handler()))
// UI public API — без auth, для встроенной console
adminHandler.RegisterPublicRoutes(r)
// SQS API — tenant auth middleware
sqsRouter := r.NewRoute().Subrouter()
sqsRouter.Use(auth.AuthMiddleware(tenantStore))
sqsRouter.HandleFunc("/", actionHandler).Methods("GET", "POST")
sqsRouter.HandleFunc("/{account}", actionHandler).Methods("GET", "POST")
sqsRouter.HandleFunc("/queue/{queueName}", actionHandler).Methods("GET", "POST")
sqsRouter.HandleFunc("/{account}/{queueName}", actionHandler).Methods("GET", "POST")
// UI console — встроенный SPA, публичный доступ
r.PathPrefix("/ui").Handler(http.StripPrefix("/ui", ui.Handler()))
return r
// SQS API — tenant auth middleware
sqsRouter := r.NewRoute().Subrouter()
sqsRouter.Use(auth.AuthMiddleware(tenantStore))
sqsRouter.HandleFunc("/", actionHandler).Methods("GET", "POST")
sqsRouter.HandleFunc("/{account}", actionHandler).Methods("GET", "POST")
sqsRouter.HandleFunc("/queue/{queueName}", actionHandler).Methods("GET", "POST")
sqsRouter.HandleFunc("/{account}/{queueName}", actionHandler).Methods("GET", "POST")
return r
}
func encodeResponse(w http.ResponseWriter, req *http.Request, statusCode int, body interfaces.AbstractResponseBody) {
protocol := resolveProtocol(req)
switch protocol {
case AwsJsonProtocol:
w.Header().Set("x-amzn-RequestId", body.GetRequestId())
w.Header().Set("Content-Type", "application/x-amz-json-1.0")
w.WriteHeader(statusCode)
if body.GetResult() == nil {
return
}
err := json.NewEncoder(w).Encode(body.GetResult())
if err != nil {
log.Errorf("Response Encoding Error: %v\nResponse: %+v", err, body)
http.Error(w, "General Error", http.StatusInternalServerError)
}
case AwsQueryProtocol:
w.Header().Set("Content-Type", "application/xml")
w.WriteHeader(statusCode)
result, err := xml.Marshal(body)
if err != nil {
log.Errorf("Response Encoding Error: %v\nResponse: %+v", err, body)
http.Error(w, "General Error", http.StatusInternalServerError)
}
_, _ = w.Write(result)
}
protocol := resolveProtocol(req)
switch protocol {
case AwsJsonProtocol:
w.Header().Set("x-amzn-RequestId", body.GetRequestId())
w.Header().Set("Content-Type", "application/x-amz-json-1.0")
w.WriteHeader(statusCode)
if body.GetResult() == nil {
return
}
err := json.NewEncoder(w).Encode(body.GetResult())
if err != nil {
log.Errorf("Response Encoding Error: %v\nResponse: %+v", err, body)
http.Error(w, "General Error", http.StatusInternalServerError)
}
case AwsQueryProtocol:
w.Header().Set("Content-Type", "application/xml")
w.WriteHeader(statusCode)
result, err := xml.Marshal(body)
if err != nil {
log.Errorf("Response Encoding Error: %v\nResponse: %+v", err, body)
http.Error(w, "General Error", http.StatusInternalServerError)
}
_, _ = w.Write(result)
}
}
// routingTableV1 — только SQS actions (SNS удалён)
var routingTableV1 = map[string]func(r *http.Request) (int, interfaces.AbstractResponseBody){
"CreateQueue": sqs.CreateQueueV1,
"ListQueues": sqs.ListQueuesV1,
"GetQueueAttributes": sqs.GetQueueAttributesV1,
"SetQueueAttributes": sqs.SetQueueAttributesV1,
"SendMessage": sqs.SendMessageV1,
"ReceiveMessage": sqs.ReceiveMessageV1,
"ChangeMessageVisibility": sqs.ChangeMessageVisibilityV1,
"DeleteMessage": sqs.DeleteMessageV1,
"GetQueueUrl": sqs.GetQueueUrlV1,
"PurgeQueue": sqs.PurgeQueueV1,
"DeleteQueue": sqs.DeleteQueueV1,
"SendMessageBatch": sqs.SendMessageBatchV1,
"DeleteMessageBatch": sqs.DeleteMessageBatchV1,
"CreateQueue": sqs.CreateQueueV1,
"ListQueues": sqs.ListQueuesV1,
"GetQueueAttributes": sqs.GetQueueAttributesV1,
"SetQueueAttributes": sqs.SetQueueAttributesV1,
"SendMessage": sqs.SendMessageV1,
"ReceiveMessage": sqs.ReceiveMessageV1,
"ChangeMessageVisibility": sqs.ChangeMessageVisibilityV1,
"DeleteMessage": sqs.DeleteMessageV1,
"GetQueueUrl": sqs.GetQueueUrlV1,
"PurgeQueue": sqs.PurgeQueueV1,
"DeleteQueue": sqs.DeleteQueueV1,
"SendMessageBatch": sqs.SendMessageBatchV1,
"DeleteMessageBatch": sqs.DeleteMessageBatchV1,
}
func health(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(200)
fmt.Fprint(w, "OK")
w.WriteHeader(200)
fmt.Fprint(w, "OK")
}
func actionHandler(w http.ResponseWriter, req *http.Request) {
action := extractAction(req)
log.WithFields(log.Fields{
"action": action,
"url": req.URL,
}).Debug("Handling URL request")
jsonFn, ok := routingTableV1[action]
if ok {
statusCode, responseBody := jsonFn(req)
encodeResponse(w, req, statusCode, responseBody)
return
}
log.Warnf("Bad Request - Action: %s", action)
w.WriteHeader(http.StatusBadRequest)
io.WriteString(w, "Bad Request")
action := extractAction(req)
log.WithFields(log.Fields{
"action": action,
"url": req.URL,
}).Debug("Handling URL request")
jsonFn, ok := routingTableV1[action]
if ok {
statusCode, responseBody := jsonFn(req)
encodeResponse(w, req, statusCode, responseBody)
return
}
log.Warnf("Bad Request - Action: %s", action)
w.WriteHeader(http.StatusBadRequest)
io.WriteString(w, "Bad Request")
}
type AwsProtocol int
const (
AwsJsonProtocol AwsProtocol = iota
AwsQueryProtocol AwsProtocol = iota
AwsJsonProtocol AwsProtocol = iota
AwsQueryProtocol AwsProtocol = iota
)
// extractAction — извлекает Action из запроса (Query Protocol или JSON Protocol)
func extractAction(req *http.Request) string {
protocol := resolveProtocol(req)
switch protocol {
case AwsJsonProtocol:
action := req.Header.Get("X-Amz-Target")
return strings.Split(action, ".")[1]
case AwsQueryProtocol:
return req.FormValue("Action")
}
return ""
protocol := resolveProtocol(req)
switch protocol {
case AwsJsonProtocol:
action := req.Header.Get("X-Amz-Target")
return strings.Split(action, ".")[1]
case AwsQueryProtocol:
return req.FormValue("Action")
}
return ""
}
// resolveProtocol — определяет протокол по Content-Type
func resolveProtocol(req *http.Request) AwsProtocol {
if req.Header.Get("Content-Type") == "application/x-amz-json-1.0" {
return AwsJsonProtocol
}
return AwsQueryProtocol
if req.Header.Get("Content-Type") == "application/x-amz-json-1.0" {
return AwsJsonProtocol
}
return AwsQueryProtocol
}