68 lines
2.1 KiB
Go
68 lines
2.1 KiB
Go
// Package router implements model-based provider routing and RBAC for the Veylant proxy.
|
|
package router
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/veylant/ia-gateway/internal/apierror"
|
|
"github.com/veylant/ia-gateway/internal/config"
|
|
)
|
|
|
|
// Canonical role names as defined in Keycloak.
|
|
const (
|
|
roleAdmin = "admin"
|
|
roleManager = "manager"
|
|
roleUser = "user"
|
|
roleAuditor = "auditor"
|
|
)
|
|
|
|
// HasAccess returns nil if a user holding roles may use model, or a 403 error.
|
|
//
|
|
// Rules (evaluated top to bottom):
|
|
// - admin / manager → unrestricted access to all models
|
|
// - auditor → 403 unless cfg.AuditorCanComplete is true
|
|
// - user → allowed only if model matches cfg.UserAllowedModels
|
|
// (exact or prefix, e.g. "gpt-4o-mini" matches "gpt-4o-mini-2024-07-18")
|
|
// - unknown role → treated as user (fail-safe)
|
|
func HasAccess(roles []string, model string, cfg *config.RBACConfig) error {
|
|
if hasRole(roles, roleAdmin) || hasRole(roles, roleManager) {
|
|
return nil
|
|
}
|
|
if hasRole(roles, roleAuditor) {
|
|
if cfg.AuditorCanComplete {
|
|
return nil
|
|
}
|
|
return apierror.NewForbiddenError("auditors are not permitted to make completion requests")
|
|
}
|
|
// User role (or any unknown role treated as user for fail-safe behaviour).
|
|
if modelMatchesAny(model, cfg.UserAllowedModels) {
|
|
return nil
|
|
}
|
|
allowed := strings.Join(cfg.UserAllowedModels, ", ")
|
|
return apierror.NewForbiddenError(
|
|
"model \"" + model + "\" is not available for your role — allowed models for your role: [" + allowed + "]. Contact your administrator to request access.",
|
|
)
|
|
}
|
|
|
|
// hasRole reports whether role appears in roles (case-insensitive).
|
|
func hasRole(roles []string, role string) bool {
|
|
for _, r := range roles {
|
|
if strings.EqualFold(r, role) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// modelMatchesAny returns true if model equals any pattern or starts with any pattern.
|
|
// This allows exact matches ("gpt-4o-mini") and prefix matches
|
|
// ("gpt-4o-mini" matches "gpt-4o-mini-2024-07-18").
|
|
func modelMatchesAny(model string, patterns []string) bool {
|
|
for _, p := range patterns {
|
|
if model == p || strings.HasPrefix(model, p) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|