package auth import ( "context" "net/http" "github.com/google/uuid" "github.com/jmoiron/sqlx" ) // Valid roles ordered by privilege level (highest first). var ValidRoles = []string{"owner", "partner", "associate", "paralegal", "secretary"} // IsValidRole checks if a role string is one of the defined roles. func IsValidRole(role string) bool { for _, r := range ValidRoles { if r == role { return true } } return false } // Permission represents an action that can be checked against roles. type Permission int const ( PermManageTeam Permission = iota PermManageBilling PermCreateCase PermEditAllCases PermEditAssignedCase PermViewAllCases PermManageDeadlines PermManageAppointments PermUploadDocuments PermDeleteDocuments PermDeleteOwnDocuments PermViewAuditLog PermManageSettings PermAIExtraction ) // rolePermissions maps each role to its set of permissions. var rolePermissions = map[string]map[Permission]bool{ "owner": { PermManageTeam: true, PermManageBilling: true, PermCreateCase: true, PermEditAllCases: true, PermEditAssignedCase: true, PermViewAllCases: true, PermManageDeadlines: true, PermManageAppointments: true, PermUploadDocuments: true, PermDeleteDocuments: true, PermDeleteOwnDocuments: true, PermViewAuditLog: true, PermManageSettings: true, PermAIExtraction: true, }, "partner": { PermManageTeam: true, PermManageBilling: true, PermCreateCase: true, PermEditAllCases: true, PermEditAssignedCase: true, PermViewAllCases: true, PermManageDeadlines: true, PermManageAppointments: true, PermUploadDocuments: true, PermDeleteDocuments: true, PermDeleteOwnDocuments: true, PermViewAuditLog: true, PermManageSettings: true, PermAIExtraction: true, }, "associate": { PermCreateCase: true, PermEditAssignedCase: true, PermViewAllCases: true, PermManageDeadlines: true, PermManageAppointments: true, PermUploadDocuments: true, PermDeleteOwnDocuments: true, PermAIExtraction: true, }, "paralegal": { PermEditAssignedCase: true, PermViewAllCases: true, PermManageDeadlines: true, PermManageAppointments: true, PermUploadDocuments: true, }, "secretary": { PermViewAllCases: true, PermManageAppointments: true, PermUploadDocuments: true, }, } // HasPermission checks if the given role has the specified permission. func HasPermission(role string, perm Permission) bool { perms, ok := rolePermissions[role] if !ok { return false } return perms[perm] } // RequirePermission returns middleware that checks if the user's role has the given permission. func RequirePermission(perm Permission) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { role := UserRoleFromContext(r.Context()) if role == "" || !HasPermission(role, perm) { writeJSONError(w, "insufficient permissions", http.StatusForbidden) return } next.ServeHTTP(w, r) }) } } // RequireRole returns middleware that checks if the user has one of the specified roles. func RequireRole(roles ...string) func(http.Handler) http.Handler { allowed := make(map[string]bool, len(roles)) for _, r := range roles { allowed[r] = true } return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { role := UserRoleFromContext(r.Context()) if !allowed[role] { writeJSONError(w, "insufficient permissions", http.StatusForbidden) return } next.ServeHTTP(w, r) }) } } // IsAssignedToCase checks if a user is assigned to a specific case. func IsAssignedToCase(ctx context.Context, db *sqlx.DB, userID, caseID uuid.UUID) (bool, error) { var exists bool err := db.GetContext(ctx, &exists, `SELECT EXISTS(SELECT 1 FROM case_assignments WHERE user_id = $1 AND case_id = $2)`, userID, caseID) return exists, err } // CanEditCase checks if a user can edit a specific case based on role and assignment. func CanEditCase(ctx context.Context, db *sqlx.DB, userID, caseID uuid.UUID, role string) (bool, error) { // Owner and partner can edit all cases if HasPermission(role, PermEditAllCases) { return true, nil } // Others need to be assigned if !HasPermission(role, PermEditAssignedCase) { return false, nil } return IsAssignedToCase(ctx, db, userID, caseID) } // CanDeleteDocument checks if a user can delete a specific document. func CanDeleteDocument(role string, docUploaderID, userID uuid.UUID) bool { if HasPermission(role, PermDeleteDocuments) { return true } if HasPermission(role, PermDeleteOwnDocuments) { return docUploaderID == userID } return false } // permissionNames maps Permission constants to their string names for frontend use. var permissionNames = map[Permission]string{ PermManageTeam: "manage_team", PermManageBilling: "manage_billing", PermCreateCase: "create_case", PermEditAllCases: "edit_all_cases", PermEditAssignedCase: "edit_assigned_case", PermViewAllCases: "view_all_cases", PermManageDeadlines: "manage_deadlines", PermManageAppointments: "manage_appointments", PermUploadDocuments: "upload_documents", PermDeleteDocuments: "delete_documents", PermDeleteOwnDocuments: "delete_own_documents", PermViewAuditLog: "view_audit_log", PermManageSettings: "manage_settings", PermAIExtraction: "ai_extraction", } // GetRolePermissions returns a list of permission name strings for the given role. func GetRolePermissions(role string) []string { perms, ok := rolePermissions[role] if !ok { return nil } var names []string for p := range perms { if name, ok := permissionNames[p]; ok { names = append(names, name) } } return names } func writeJSONError(w http.ResponseWriter, msg string, status int) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) w.Write([]byte(`{"error":"` + msg + `"}`)) }