Files
pulse/internal/api/apihttp/scope.go
2026-08-21 14:56:04 +01:00

67 lines
1.6 KiB
Go

package apihttp
import (
"encoding/json"
"net/http"
"strings"
internalauth "github.com/rcourtman/pulse-go-rewrite/pkg/auth"
)
// EnsureScope admits sessions and API tokens carrying the required scope.
func EnsureScope(w http.ResponseWriter, r *http.Request, scope string) bool {
return EnsureAnyScope(w, r, scope)
}
// EnsureAnyScope admits sessions and API tokens carrying any required scope.
func EnsureAnyScope(w http.ResponseWriter, r *http.Request, scopes ...string) bool {
normalized := make([]string, 0, len(scopes))
for _, scope := range scopes {
scope = strings.TrimSpace(scope)
if scope == "" {
return true
}
normalized = append(normalized, scope)
}
if len(normalized) == 0 {
return true
}
record := internalauth.GetAPIToken(r.Context())
if record == nil {
return true
}
for _, scope := range normalized {
if record.HasScope(scope) {
return true
}
}
if len(normalized) == 1 {
RespondMissingScope(w, normalized[0])
return false
}
if w != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusForbidden)
_ = json.NewEncoder(w).Encode(map[string]any{
"error": "missing_scope",
"requiredScopes": normalized,
})
}
return false
}
// RespondMissingScope writes the canonical single-scope denial envelope.
func RespondMissingScope(w http.ResponseWriter, scope string) {
if w == nil {
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusForbidden)
_ = json.NewEncoder(w).Encode(map[string]any{
"error": "missing_scope",
"requiredScope": scope,
})
}