web-production-saas-starter/go-b2b-starter/src/pkg/auth/rbac.go
2025-12-17 21:00:38 +04:00

437 lines
14 KiB
Go

package auth
// =============================================================================
// RBAC DEFINITIONS - Roles and Permissions
// =============================================================================
//
// This file is the SINGLE SOURCE OF TRUTH for all role and permission definitions.
// Customize these for your business domain.
//
// =============================================================================
// =============================================================================
// PERMISSIONS - Customize for your business domain
// =============================================================================
//
// The default uses "resource" as a generic placeholder.
// Change "resource" to match your domain entity:
//
// E-commerce: "product:view", "product:create", "order:manage"
// Healthcare: "patient:view", "records:manage", "prescription:create"
// Project Mgmt: "project:view", "task:create", "task:assign"
// CRM: "contact:view", "deal:create", "deal:close"
// Invoice System: "invoice:view", "invoice:create", "invoice:approve"
//
// Simply rename "resource" to your domain entity!
//
// =============================================================================
var (
// Resource permissions - rename "resource" to your domain entity
PermResourceView = NewPermission("resource", "view")
PermResourceCreate = NewPermission("resource", "create")
PermResourceEdit = NewPermission("resource", "edit")
PermResourceDelete = NewPermission("resource", "delete")
PermResourceApprove = NewPermission("resource", "approve")
// Organization permissions
PermOrgView = NewPermission("org", "view")
PermOrgManage = NewPermission("org", "manage")
)
// AllPermissions is the complete list of all permissions in the system.
// Update this when you add or remove permissions.
var AllPermissions = []Permission{
PermResourceView,
PermResourceCreate,
PermResourceEdit,
PermResourceDelete,
PermResourceApprove,
PermOrgView,
PermOrgManage,
}
// =============================================================================
// ROLES - Customize role names and permissions as needed
// =============================================================================
//
// Default roles follow a simple hierarchy:
// - Member: Basic access (view, create)
// - Manager: Elevated access (edit, delete, approve)
// - Admin: Full control (everything + org management)
//
// To customize:
// 1. Change role IDs if needed (must match Stytch configuration)
// 2. Adjust permissions for each role
// 3. Add new roles if needed
//
// =============================================================================
// RoleInfo contains complete information about a role including its permissions.
// Used for API responses and role lookups.
type RoleInfo struct {
// ID is the unique identifier for the role (e.g., "member", "manager", "admin")
ID string
// Name is the display name for the role
Name string
// Description explains the purpose and scope of the role
Description string
// Permissions is the list of permissions granted to this role
Permissions []Permission
}
var (
// RoleMemberInfo - Basic user access
// Typical users: Employees, staff, basic users
RoleMemberInfo = RoleInfo{
ID: "member",
Name: "Member",
Description: "Basic access. Can view and create resources.",
Permissions: []Permission{
PermResourceView,
PermResourceCreate,
},
}
// RoleManagerInfo - Elevated access with approval rights
// Typical users: Team leads, supervisors, managers
RoleManagerInfo = RoleInfo{
ID: "manager",
Name: "Manager",
Description: "Elevated access. Can edit, delete, and approve resources.",
Permissions: []Permission{
PermResourceView,
PermResourceCreate,
PermResourceEdit,
PermResourceDelete,
PermResourceApprove,
PermOrgView,
},
}
// RoleAdminInfo - Full system control
// Typical users: Business owners, administrators
RoleAdminInfo = RoleInfo{
ID: "admin",
Name: "Admin",
Description: "Full control. Can manage organization settings and users.",
Permissions: []Permission{
PermResourceView,
PermResourceCreate,
PermResourceEdit,
PermResourceDelete,
PermResourceApprove,
PermOrgView,
PermOrgManage,
},
}
)
// AllRoles is the complete list of all roles in the RBAC system.
// Update this when you add or remove roles.
var AllRoles = []RoleInfo{
RoleMemberInfo,
RoleManagerInfo,
RoleAdminInfo,
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
// GetRoleInfo retrieves role information by role ID.
// Returns nil if the role is not found.
func GetRoleInfo(roleID string) *RoleInfo {
for i := range AllRoles {
if AllRoles[i].ID == roleID {
return &AllRoles[i]
}
}
return nil
}
// GetRolePermissionIDs returns just the permission IDs (strings) for a given role.
// Useful for Stytch integration and API responses.
func GetRolePermissionIDs(roleID string) []string {
role := GetRoleInfo(roleID)
if role == nil {
return []string{}
}
ids := make([]string, len(role.Permissions))
for i, perm := range role.Permissions {
ids[i] = string(perm)
}
return ids
}
// HasPermission checks if a role has a specific permission.
func HasPermission(roleID string, permission Permission) bool {
role := GetRoleInfo(roleID)
if role == nil {
return false
}
for _, perm := range role.Permissions {
if perm == permission {
return true
}
}
return false
}
// =============================================================================
// PERMISSION MATRIX (for reference)
// =============================================================================
//
// | Permission | Member | Manager | Admin |
// |-------------------|--------|---------|-------|
// | resource:view | ✓ | ✓ | ✓ |
// | resource:create | ✓ | ✓ | ✓ |
// | resource:edit | | ✓ | ✓ |
// | resource:delete | | ✓ | ✓ |
// | resource:approve | | ✓ | ✓ |
// | org:view | | ✓ | ✓ |
// | org:manage | | | ✓ |
//
// Role totals:
// - Member: 2 permissions
// - Manager: 6 permissions
// - Admin: 7 permissions (all)
//
// =============================================================================
// =============================================================================
// API RESPONSE TYPES (DTOs)
// =============================================================================
// PermissionDTO represents a permission in API responses
type PermissionDTO struct {
ID string `json:"id"`
Resource string `json:"resource"`
Action string `json:"action"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
Category string `json:"category"`
}
// NewPermissionDTO converts a Permission to a DTO
func NewPermissionDTO(perm Permission) PermissionDTO {
return PermissionDTO{
ID: string(perm),
Resource: perm.Resource(),
Action: perm.Action(),
// Generic display name and description for simple permissions
DisplayName: perm.Resource() + " " + perm.Action(),
Description: "Can " + perm.Action() + " " + perm.Resource(),
Category: "General",
}
}
// RoleDTO represents a role with its permissions in API responses
type RoleDTO struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Permissions []PermissionDTO `json:"permissions"`
}
// NewRoleDTO converts a RoleInfo to a DTO
func NewRoleDTO(role RoleInfo) RoleDTO {
permDTOs := make([]PermissionDTO, len(role.Permissions))
for i, perm := range role.Permissions {
permDTOs[i] = NewPermissionDTO(perm)
}
return RoleDTO{
ID: role.ID,
Name: role.Name,
Description: role.Description,
Permissions: permDTOs,
}
}
// RolesResponse is the response body for GET /rbac/roles
type RolesResponse struct {
Roles []RoleDTO `json:"roles"`
}
// PermissionsResponse is the response body for GET /rbac/permissions
type PermissionsResponse struct {
Permissions []PermissionDTO `json:"permissions"`
}
// PermissionsByCategoryResponse is the response body for GET /rbac/permissions/by-category
type PermissionsByCategoryResponse struct {
Categories map[string][]PermissionDTO `json:"categories"`
}
// RolePermissionsResponse contains role information with detailed metadata
type RolePermissionsResponse struct {
Role RoleDTO `json:"role"`
Statistics RoleStatistics `json:"statistics"`
Restrictions RoleRestrictions `json:"restrictions"`
}
// RoleStatistics provides summary information about a role
type RoleStatistics struct {
TotalPermissions int `json:"total_permissions"`
CanApprove bool `json:"can_approve"`
CanManageOrg bool `json:"can_manage_org"`
Description string `json:"description"`
}
// RoleRestrictions documents what a role cannot do
type RoleRestrictions struct {
CannotDo []string `json:"cannot_do"`
DataAccessLevel string `json:"data_access_level"`
Scope string `json:"scope"`
}
// NewRolePermissionsResponse creates a detailed response for a role
func NewRolePermissionsResponse(roleID string) *RolePermissionsResponse {
role := GetRoleInfo(roleID)
if role == nil {
return nil
}
stats := RoleStatistics{
TotalPermissions: len(role.Permissions),
CanApprove: HasPermission(roleID, PermResourceApprove),
CanManageOrg: HasPermission(roleID, PermOrgManage),
Description: role.Description,
}
// Define restrictions based on role
var restrictions RoleRestrictions
switch roleID {
case "member":
restrictions = RoleRestrictions{
CannotDo: []string{"Edit resources", "Delete resources", "Approve requests", "Manage organization"},
DataAccessLevel: "Basic - view and create only",
Scope: "Limited to own resources",
}
case "manager":
restrictions = RoleRestrictions{
CannotDo: []string{"Manage organization settings"},
DataAccessLevel: "Elevated - full resource access",
Scope: "Team-wide access",
}
case "admin":
restrictions = RoleRestrictions{
CannotDo: []string{},
DataAccessLevel: "Full - all data access",
Scope: "Organization-wide",
}
default:
restrictions = RoleRestrictions{
CannotDo: []string{},
DataAccessLevel: "Unknown",
Scope: "Unknown",
}
}
return &RolePermissionsResponse{
Role: NewRoleDTO(*role),
Statistics: stats,
Restrictions: restrictions,
}
}
// PermissionCheckRequest is used to verify if a role has a permission
type PermissionCheckRequest struct {
RoleID string `json:"role_id" binding:"required"`
PermissionID string `json:"permission_id" binding:"required"`
}
// PermissionCheckResponse indicates whether a role has a permission
type PermissionCheckResponse struct {
RoleID string `json:"role_id"`
PermissionID string `json:"permission_id"`
HasPermission bool `json:"has_permission"`
}
// RBACMetadata provides summary information about the RBAC system
type RBACMetadata struct {
TotalRoles int `json:"total_roles"`
TotalPermissions int `json:"total_permissions"`
PermissionsByRole map[string]int `json:"permissions_by_role"`
Description string `json:"description"`
}
// NewRBACMetadata creates metadata about the RBAC system
func NewRBACMetadata() RBACMetadata {
permsByRole := make(map[string]int)
for _, role := range AllRoles {
permsByRole[role.ID] = len(role.Permissions)
}
return RBACMetadata{
TotalRoles: len(AllRoles),
TotalPermissions: len(AllPermissions),
PermissionsByRole: permsByRole,
Description: "Simple RBAC system with 3 roles (Member, Manager, Admin) and 7 generic permissions",
}
}
// =============================================================================
// SERVICE INTERFACE AND IMPLEMENTATION
// =============================================================================
// RBACService provides business logic for RBAC operations
type RBACService interface {
GetAllRoles() []RoleInfo
GetRoleInfo(roleID string) *RoleInfo
GetAllPermissions() []Permission
GetRolePermissions(roleID string) []Permission
GetPermissionsByCategory() map[string][]Permission
GetPermissionsByRoleID(roleID string) []string
HasPermission(roleID string, permissionID string) bool
GetRBACMetadata() RBACMetadata
}
// defaultRBACService implements the RBACService interface
type defaultRBACService struct{}
func NewRBACService() RBACService {
return &defaultRBACService{}
}
func (s *defaultRBACService) GetAllRoles() []RoleInfo {
return AllRoles
}
func (s *defaultRBACService) GetRoleInfo(roleID string) *RoleInfo {
return GetRoleInfo(roleID)
}
func (s *defaultRBACService) GetAllPermissions() []Permission {
return AllPermissions
}
func (s *defaultRBACService) GetRolePermissions(roleID string) []Permission {
role := GetRoleInfo(roleID)
if role == nil {
return []Permission{}
}
return role.Permissions
}
func (s *defaultRBACService) GetPermissionsByCategory() map[string][]Permission {
// For simplicity, return all permissions in one "General" category
return map[string][]Permission{
"General": AllPermissions,
}
}
func (s *defaultRBACService) GetPermissionsByRoleID(roleID string) []string {
return GetRolePermissionIDs(roleID)
}
func (s *defaultRBACService) HasPermission(roleID string, permissionID string) bool {
return HasPermission(roleID, Permission(permissionID))
}
func (s *defaultRBACService) GetRBACMetadata() RBACMetadata {
return NewRBACMetadata()
}