feat: enhance evobgp with new command-line tools for bundle management, including pull, verify, and apply functionalities. Update go.mod to include necessary dependencies and complete todos in architecture plan for improved observability and deployment practices.
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ctxKey int
|
||||
|
||||
const authCtxKey ctxKey = 1
|
||||
|
||||
// Auth holds resolved API identity for a request.
|
||||
type Auth struct {
|
||||
TenantID string
|
||||
Role string // viewer, editor, operator, node
|
||||
Token string
|
||||
}
|
||||
|
||||
func authFromContext(ctx context.Context) (Auth, bool) {
|
||||
a, ok := ctx.Value(authCtxKey).(Auth)
|
||||
return a, ok
|
||||
}
|
||||
|
||||
type apiKeyRecord struct {
|
||||
token string
|
||||
tenantID string
|
||||
role string
|
||||
}
|
||||
|
||||
func parseAPIKeysSpec(spec string) []apiKeyRecord {
|
||||
spec = strings.TrimSpace(spec)
|
||||
if spec == "" {
|
||||
return nil
|
||||
}
|
||||
var out []apiKeyRecord
|
||||
for _, part := range strings.Split(spec, ",") {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
fields := strings.Split(part, "|")
|
||||
if len(fields) != 3 {
|
||||
continue
|
||||
}
|
||||
out = append(out, apiKeyRecord{
|
||||
token: strings.TrimSpace(fields[0]),
|
||||
tenantID: strings.TrimSpace(fields[1]),
|
||||
role: strings.TrimSpace(fields[2]),
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *Server) authMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if s.insecureDev {
|
||||
h := r.Header.Get("Authorization")
|
||||
const p = "Bearer "
|
||||
if strings.HasPrefix(h, p) {
|
||||
tok := strings.TrimSpace(strings.TrimPrefix(h, p))
|
||||
if tok == "dev" {
|
||||
if a, ok := s.devAuth(); ok {
|
||||
r = r.WithContext(context.WithValue(r.Context(), authCtxKey, a))
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
h := r.Header.Get("Authorization")
|
||||
const p = "Bearer "
|
||||
if !strings.HasPrefix(h, p) {
|
||||
writeProblem(w, http.StatusUnauthorized, "Unauthorized", "missing or invalid bearer token")
|
||||
return
|
||||
}
|
||||
raw := strings.TrimSpace(strings.TrimPrefix(h, p))
|
||||
var matched *apiKeyRecord
|
||||
for i := range s.apiKeys {
|
||||
if s.apiKeys[i].token == raw {
|
||||
matched = &s.apiKeys[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if matched == nil {
|
||||
writeProblem(w, http.StatusUnauthorized, "Unauthorized", "unknown api key")
|
||||
return
|
||||
}
|
||||
a := Auth{TenantID: matched.tenantID, Role: matched.role, Token: raw}
|
||||
r = r.WithContext(context.WithValue(r.Context(), authCtxKey, a))
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) devAuth() (Auth, bool) {
|
||||
tid, _, _, _, _ := s.store.DemoIDs()
|
||||
if tid == "" {
|
||||
return Auth{}, false
|
||||
}
|
||||
return Auth{TenantID: tid, Role: "operator", Token: "dev"}, true
|
||||
}
|
||||
|
||||
func roleLevel(role string) int {
|
||||
switch strings.ToLower(role) {
|
||||
case "viewer":
|
||||
return 1
|
||||
case "editor":
|
||||
return 2
|
||||
case "operator":
|
||||
return 3
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// requireAtLeast rejects node role and enforces viewer/editor/operator ladder.
|
||||
func (s *Server) requireAtLeast(w http.ResponseWriter, a Auth, need string) bool {
|
||||
if strings.ToLower(a.Role) == "node" {
|
||||
writeProblem(w, http.StatusForbidden, "Forbidden", "node role cannot access this resource")
|
||||
return false
|
||||
}
|
||||
if roleLevel(a.Role) < roleLevel(need) {
|
||||
writeProblem(w, http.StatusForbidden, "Forbidden", "insufficient role")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Server) requireNode(w http.ResponseWriter, a Auth) bool {
|
||||
if strings.ToLower(a.Role) != "node" {
|
||||
writeProblem(w, http.StatusForbidden, "Forbidden", "node role required")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user