Authority | Goffee
Description: 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.

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:

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:

#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"))