Authority
Goffee comes with a built-in roles and access control system (utils/authority.go). It uses a many-to-many relationship between users, roles, and permissions to provide granular access control.
Overview
The authority system consists of three database models:
- Roles - Named groups (e.g., "admin", "editor", "subscriber")
- Permissions - Individual actions (e.g., "create-post", "delete-user")
- UserRoles - Links users to roles (many-to-many)
- RolePermissions - Links permissions to roles (many-to-many)
Getting started
First, ensure your database models are migrated. The authority system requires these tables:
rolespermissionsuser_rolesrole_permissions
These are defined in models/role.go, models/permission.go, models/user-roles.go, and models/role-permissions.go respectively. Run auto-migrations to create them:
// In run-auto-migrations.go
func RunAutoMigrations() {
app.GetGorm().AutoMigrate(
&models.User{},
&models.Role{},
&models.Permission{},
&models.UserRole{},
&models.RolePermission{},
)
}
Creating roles and permissions
Use the Authority struct from utils/authority.go:
package controllers
import (
"git.smarteching.com/goffee/core"
"git.smarteching.com/goffee/cup/models"
"git.smarteching.com/goffee/cup/utils"
)
func SetupRoles(c *core.Context) *core.Response {
auth := &utils.Authority{}
// Create roles
err := auth.CreateRole(c, models.Role{
Name: "Administrator",
Slug: "admin",
})
if err != nil {
return c.Response.Status(400).Json(map[string]string{
"error": err.Error(),
})
}
err = auth.CreateRole(c, models.Role{
Name: "Editor",
Slug: "editor",
})
if err != nil {
return c.Response.Status(400).Json(map[string]string{
"error": err.Error(),
})
}
// Create permissions
err = auth.CreatePermission(c, models.Permission{
Name: "Create Post",
Slug: "create-post",
})
if err != nil {
return c.Response.Status(400).Json(map[string]string{
"error": err.Error(),
})
}
err = auth.CreatePermission(c, models.Permission{
Name: "Delete User",
Slug: "delete-user",
})
if err != nil {
return c.Response.Status(400).Json(map[string]string{
"error": err.Error(),
})
}
return c.Response.Json(`{"message": "roles and permissions created"}`)
}
Assigning permissions to roles
Once roles and permissions exist, assign permissions to roles:
auth := &utils.Authority{}
// Assign multiple permissions to the admin role
err := auth.AssignPermissionsToRole(c, "admin", []string{
"create-post",
"edit-post",
"delete-post",
"delete-user",
})
If a permission or role is not found, the method returns ErrPermissionNotFound or ErrRoleNotFound. Duplicate assignments are rejected with an error message.
Assigning roles to users
Assign a role to a user by their user ID:
auth := &utils.Authority{}
err := auth.AssignRoleToUser(c, userID, "admin")
if err != nil {
// handle error (role not found, already assigned, etc.)
}
A user can have multiple roles assigned:
auth.AssignRoleToUser(c, userID, "editor")
auth.AssignRoleToUser(c, userID, "subscriber")
Checking permissions
Check if a user has a specific role
hasRole, err := auth.CheckUserRole(c, userID, "admin")
if hasRole {
// user is an admin
}
Check if a user has a specific permission
This checks across all roles assigned to the user:
canDelete, err := auth.CheckUserPermission(c, userID, "delete-post")
if canDelete {
// user has the delete-post permission via one of their roles
}
Check if a role has a specific permission
hasPerm, err := auth.CheckRolePermission(c, "editor", "create-post")
if hasPerm {
// the editor role has the create-post permission
}
Revoking permissions
Revoke a permission from a role
err := auth.RevokeRolePermission(c, "editor", "create-post")
Revoke a role from a user
err := auth.RevokeUserRole(c, userID, "editor")
Revoke all roles from a user
err := auth.RevokeAllUserRole(c, userID)
Listing data
Get all roles
roles, err := auth.GetAllRoles(c)
// roles is a []models.Role
Get all permissions
permissions, err := auth.GetAllPermissions(c)
// permissions is a []models.Permission
Get roles assigned to a user
userRoles, err := auth.GetUserRoles(c, userID)
// userRoles is a []models.Role
Get permissions assigned to a role
rolePerms, err := auth.GetRolePermissions(c, "admin")
// rolePerms is a []models.Permission
Deleting roles and permissions
Delete a role
Deleting a role also removes its permission assignments. The role must not be assigned to any user.
err := auth.DeleteRole(c, "editor")
// Returns ErrRoleInUse if the role is assigned to a user
Delete a permission
The permission must not be assigned to any role.
err := auth.DeletePermission(c, "create-post")
// Returns ErrPermissionInUse if the permission is assigned to a role
Error handling
The authority system defines several sentinel errors for common failure cases:
| Error | Meaning |
|---|---|
ErrPermissionInUse | Cannot delete a permission that is assigned to a role |
ErrPermissionNotFound | The specified permission slug does not exist |
ErrRoleInUse | Cannot delete a role that is assigned to a user |
ErrRoleNotFound | The specified role slug does not exist |
Complete example with middleware
Here is a complete example that combines authority checks with a middleware hook:
package hooks
import (
"strconv"
"git.smarteching.com/goffee/core"
"git.smarteching.com/goffee/cup/utils"
)
// RequireRole returns a hook that checks if the authenticated user has the given role
func RequireRole(roleSlug string) core.Hook {
return func(c *core.Context) *core.Response {
// Get the authenticated user ID from the session
session := &utils.SessionUser{}
if !session.Init(c) {
return c.Response.Status(401).Json(`{"error": "not authenticated"}`)
}
auth := &utils.Authority{}
hasRole, err := auth.CheckUserRole(c, session.GetUserID(), roleSlug)
if err != nil || !hasRole {
return c.Response.Status(403).Json(`{"error": "forbidden"}`)
}
// Continue to the next handler
return nil
}
}
Register the hook in your routes:
router.Get("/admin/dashboard", controllers.AdminDashboard, hooks.RequireRole("admin"))