// 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 }