Files
EvoBGP/internal/httpapi/permissions.go
T
DenozordecandCursor 4d83b8d673
CI / changes (push) Successful in 6s
CI / commitlint (push) Skipped
CI / openapi (push) Successful in 27s
CI / web (push) Successful in 51s
CI / go (push) Successful in 2m19s
CI / bird2 (push) Successful in 13s
CI / release (push) Successful in 4m24s
feat(auth): integrate portal JWT for enhanced authentication and authorization
Added support for portal JWT authentication, enabling single sign-on (SSO) capabilities. Updated the application to handle JWT claims for user permissions and roles, enhancing security and access control. Refactored relevant components and API routes to accommodate the new authentication flow, ensuring a seamless user experience. Updated documentation to reflect the new authentication requirements and configurations.

Co-authored-by: Cursor <[email protected]>
2026-07-18 23:23:52 +07:00

108 lines
2.8 KiB
Go

package httpapi
import (
"net/http"
"strings"
)
// Portal permission strings. Format: <app>:<section>:<level> (bgp:modules:read).
// Superset order: admin ⊃ write ⊃ read for the same <app>:<section>.
const (
permLevelRead = "read"
permLevelWrite = "write"
permLevelAdmin = "admin"
)
// permLevelRank returns 0 for unknown, 1 for read, 2 for write, 3 for admin.
func permLevelRank(level string) int {
switch strings.ToLower(strings.TrimSpace(level)) {
case permLevelRead:
return 1
case permLevelWrite:
return 2
case permLevelAdmin:
return 3
default:
return 0
}
}
// splitPerm splits a permission string into (app, section, level).
func splitPerm(perm string) (app, section, level string, ok bool) {
parts := strings.Split(strings.TrimSpace(perm), ":")
if len(parts) != 3 {
return "", "", "", false
}
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), strings.TrimSpace(parts[2]), true
}
// HasPermission reports whether the granted list satisfies required, applying the
// admin ⊃ write ⊃ read superset within the same app+section.
func HasPermission(granted []string, required string) bool {
rApp, rSection, rLevel, ok := splitPerm(required)
if !ok {
return false
}
needRank := permLevelRank(rLevel)
if needRank == 0 {
return false
}
for _, g := range granted {
gApp, gSection, gLevel, ok := splitPerm(g)
if !ok {
continue
}
if !strings.EqualFold(gApp, rApp) || !strings.EqualFold(gSection, rSection) {
continue
}
if permLevelRank(gLevel) >= needRank {
return true
}
}
return false
}
// permAPIKeyRoleFor maps a permission level to the API-key role required.
func permAPIKeyRoleFor(perm string) string {
_, _, level, ok := splitPerm(perm)
if !ok {
return "operator"
}
switch strings.ToLower(level) {
case permLevelRead:
return "viewer"
case permLevelWrite:
return "editor"
case permLevelAdmin:
return "operator"
default:
return "operator"
}
}
// requirePerm enforces a permission for a portal JWT or falls back to the API-key role ladder.
// node/firewall roles are always rejected (they use requireNode / requireFirewall).
func (s *Server) requirePerm(w http.ResponseWriter, a Auth, perm string) bool {
switch strings.ToLower(a.Role) {
case "node":
writeProblem(w, http.StatusForbidden, "Forbidden", "node role cannot access this resource")
return false
case "firewall":
writeProblem(w, http.StatusForbidden, "Forbidden", "firewall role cannot access this resource")
return false
}
if a.Kind == AuthKindJWT || len(a.Permissions) > 0 {
if a.IsAdmin || HasPermission(a.Permissions, perm) {
return true
}
writeProblem(w, http.StatusForbidden, "Forbidden", "missing permission: "+perm)
return false
}
need := permAPIKeyRoleFor(perm)
if roleLevel(a.Role) < roleLevel(need) {
writeProblem(w, http.StatusForbidden, "Forbidden", "insufficient role")
return false
}
return true
}