RBAC System (internal/auth/):
- Permission constants for all resources (dashboard, agents, alerts, etc.)
- Wildcard permission support ("*" for admin, "category:*" for groups)
- Authorizer service with role-based permission checking
- RequirePermission middleware for route protection
Role Management API:
- GET /roles - List all roles
- GET /roles/:id - Get role details
- POST /roles - Create custom role
- PUT /roles/:id - Update role (custom only)
- DELETE /roles/:id - Delete role (custom only)
User Management API (admin):
- GET /users - List all users
- GET /users/:id - Get user details
- GET /users/:id/roles - Get user's roles
- POST /users - Create new user
- PUT /users/:id - Update user profile
- DELETE /users/:id - Disable user account
- POST /users/:id/enable - Re-enable user
- POST /users/:id/reset-password - Reset password
- PUT /users/:id/roles - Assign roles to user
Built-in Roles (via database migrations):
- admin: Full access (*)
- operator: Agent and alert management
- viewer: Read-only dashboard access
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
244 lines
5.7 KiB
Go
244 lines
5.7 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"tyto/internal/database"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Permission represents a single permission.
|
|
type Permission string
|
|
|
|
// Permission constants for Tyto resources.
|
|
const (
|
|
// Dashboard permissions
|
|
PermDashboardView Permission = "dashboard:view"
|
|
|
|
// Agent permissions
|
|
PermAgentsView Permission = "agents:view"
|
|
PermAgentsManage Permission = "agents:manage" // Add, remove, approve
|
|
|
|
// Alert permissions
|
|
PermAlertsView Permission = "alerts:view"
|
|
PermAlertsAcknowledge Permission = "alerts:acknowledge"
|
|
PermAlertsConfigure Permission = "alerts:configure"
|
|
|
|
// Metrics permissions
|
|
PermMetricsQuery Permission = "metrics:query"
|
|
PermMetricsExport Permission = "metrics:export"
|
|
|
|
// User permissions
|
|
PermUsersView Permission = "users:view"
|
|
PermUsersManage Permission = "users:manage" // Create, update, disable
|
|
|
|
// Role permissions
|
|
PermRolesView Permission = "roles:view"
|
|
PermRolesManage Permission = "roles:manage"
|
|
|
|
// Settings permissions
|
|
PermSettingsView Permission = "settings:view"
|
|
PermSettingsManage Permission = "settings:manage"
|
|
|
|
// PKI permissions
|
|
PermPKIManage Permission = "pki:manage"
|
|
|
|
// Audit log permissions
|
|
PermAuditView Permission = "audit:view"
|
|
)
|
|
|
|
// AllPermissions returns all defined permissions.
|
|
func AllPermissions() []Permission {
|
|
return []Permission{
|
|
PermDashboardView,
|
|
PermAgentsView,
|
|
PermAgentsManage,
|
|
PermAlertsView,
|
|
PermAlertsAcknowledge,
|
|
PermAlertsConfigure,
|
|
PermMetricsQuery,
|
|
PermMetricsExport,
|
|
PermUsersView,
|
|
PermUsersManage,
|
|
PermRolesView,
|
|
PermRolesManage,
|
|
PermSettingsView,
|
|
PermSettingsManage,
|
|
PermPKIManage,
|
|
PermAuditView,
|
|
}
|
|
}
|
|
|
|
// Authorizer provides authorization checking.
|
|
type Authorizer struct {
|
|
db database.Database
|
|
}
|
|
|
|
// NewAuthorizer creates a new authorizer.
|
|
func NewAuthorizer(db database.Database) *Authorizer {
|
|
return &Authorizer{db: db}
|
|
}
|
|
|
|
// HasPermission checks if a user has a specific permission.
|
|
func (a *Authorizer) HasPermission(ctx context.Context, userID string, perm Permission) bool {
|
|
roles, err := a.db.GetUserRoles(ctx, userID)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return a.checkRolesPermission(roles, perm)
|
|
}
|
|
|
|
// HasAnyPermission checks if a user has any of the specified permissions.
|
|
func (a *Authorizer) HasAnyPermission(ctx context.Context, userID string, perms ...Permission) bool {
|
|
roles, err := a.db.GetUserRoles(ctx, userID)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
for _, perm := range perms {
|
|
if a.checkRolesPermission(roles, perm) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// HasAllPermissions checks if a user has all specified permissions.
|
|
func (a *Authorizer) HasAllPermissions(ctx context.Context, userID string, perms ...Permission) bool {
|
|
roles, err := a.db.GetUserRoles(ctx, userID)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
for _, perm := range perms {
|
|
if !a.checkRolesPermission(roles, perm) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// GetUserPermissions returns all permissions for a user.
|
|
func (a *Authorizer) GetUserPermissions(ctx context.Context, userID string) []Permission {
|
|
roles, err := a.db.GetUserRoles(ctx, userID)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
permSet := make(map[Permission]bool)
|
|
for _, role := range roles {
|
|
for _, p := range role.Permissions {
|
|
if p == "*" {
|
|
// Wildcard - return all permissions
|
|
return AllPermissions()
|
|
}
|
|
permSet[Permission(p)] = true
|
|
}
|
|
}
|
|
|
|
perms := make([]Permission, 0, len(permSet))
|
|
for p := range permSet {
|
|
perms = append(perms, p)
|
|
}
|
|
return perms
|
|
}
|
|
|
|
// checkRolesPermission checks if any of the roles grant the permission.
|
|
func (a *Authorizer) checkRolesPermission(roles []*database.Role, perm Permission) bool {
|
|
permStr := string(perm)
|
|
|
|
for _, role := range roles {
|
|
for _, p := range role.Permissions {
|
|
// Exact match
|
|
if p == permStr {
|
|
return true
|
|
}
|
|
|
|
// Wildcard match: "*" matches everything
|
|
if p == "*" {
|
|
return true
|
|
}
|
|
|
|
// Category wildcard: "agents:*" matches "agents:view", "agents:manage"
|
|
if strings.HasSuffix(p, ":*") {
|
|
prefix := strings.TrimSuffix(p, "*")
|
|
if strings.HasPrefix(permStr, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// RequirePermission returns middleware that requires a specific permission.
|
|
func (a *Authorizer) RequirePermission(perm Permission) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
user := GetUser(c)
|
|
if user == nil {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
"error": "authentication required",
|
|
})
|
|
return
|
|
}
|
|
|
|
if !a.HasPermission(c.Request.Context(), user.ID, perm) {
|
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
|
"error": "insufficient permissions",
|
|
})
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// RequireAnyPermission returns middleware that requires any of the specified permissions.
|
|
func (a *Authorizer) RequireAnyPermission(perms ...Permission) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
user := GetUser(c)
|
|
if user == nil {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
"error": "authentication required",
|
|
})
|
|
return
|
|
}
|
|
|
|
if !a.HasAnyPermission(c.Request.Context(), user.ID, perms...) {
|
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
|
"error": "insufficient permissions",
|
|
})
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// RequireAllPermissions returns middleware that requires all specified permissions.
|
|
func (a *Authorizer) RequireAllPermissions(perms ...Permission) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
user := GetUser(c)
|
|
if user == nil {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
"error": "authentication required",
|
|
})
|
|
return
|
|
}
|
|
|
|
if !a.HasAllPermissions(c.Request.Context(), user.ID, perms...) {
|
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
|
"error": "insufficient permissions",
|
|
})
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|