Skip to main content

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:

  • roles
  • permissions
  • user_roles
  • role_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:

#file: controllers/adminusers.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:

ErrorMeaning
ErrPermissionInUseCannot delete a permission that is assigned to a role
ErrPermissionNotFoundThe specified permission slug does not exist
ErrRoleInUseCannot delete a role that is assigned to a user
ErrRoleNotFoundThe specified role slug does not exist

Complete example with middleware

Here is a complete example that combines authority checks with a middleware hook:

#file: hooks/auth-check.go
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:

#file: routes.go
router.Get("/admin/dashboard", controllers.AdminDashboard, hooks.RequireRole("admin"))