Authorization

Authorization in Nimbus determines what an authenticated user is allowed to do. While authentication verifies identity, authorization checks permissions. Nimbus provides the auth.Policy interface for fine-grained access control.

The Policy interface

A policy checks whether a user can perform an action on a resource:

type Policy interface {
    Allow(ctx context.Context, user User, action string, resource any) bool
}

Defining a policy

Create a struct that implements auth.Policy. Check the action and compare the user to the resource:

type PostPolicy struct{}

func (p *PostPolicy) Allow(ctx context.Context, user auth.User, action string, resource any) bool {
    post := resource.(*Post)
    switch action {
    case "view":
        return true
    case "update", "delete":
        return post.UserID == user.GetID()
    default:
        return false
    }
}

Using PolicyFunc

For simple checks, use auth.PolicyFunc to create a policy from a plain function:

adminOnly := auth.PolicyFunc(func(ctx context.Context, user auth.User, action string, resource any) bool {
    appUser := user.(*AppUser)
    return appUser.Role == "admin"
})

Registering policies

Keep a map of policies indexed by resource type so handlers can look them up by name:

var policies = map[string]auth.Policy{
    "post":    &PostPolicy{},
    "comment": &CommentPolicy{},
    "admin":   adminOnly,
}

Checking permissions in handlers

Retrieve the user from context, fetch the resource, then call Allow on the appropriate policy:

func UpdatePost(c *http.Context) error {
    user := auth.UserFromContext(c.Request.Context())

    var post Post
    db.First(&post, c.Param("id"))

    policy := policies["post"]
    if !policy.Allow(c.Request.Context(), user, "update", &post) {
        return c.JSON(403, map[string]string{"error": "forbidden"})
    }

    // proceed with update ...
    return c.JSON(200, map[string]string{"status": "updated"})
}

Role-based access control

You can build role-based authorization on top of policies. Add a Role field to your user model and check it in the policy:

type RolePolicy struct {
    AllowedRoles []string
}

func (p *RolePolicy) Allow(ctx context.Context, user auth.User, action string, resource any) bool {
    appUser := user.(*AppUser)
    for _, role := range p.AllowedRoles {
        if appUser.Role == role {
            return true
        }
    }
    return false
}

Gate pattern

For quick inline checks without a full policy struct, use PolicyFunc as a gate:

canPublish := auth.PolicyFunc(func(ctx context.Context, user auth.User, action string, resource any) bool {
    appUser := user.(*AppUser)
    return appUser.Role == "admin" || appUser.Role == "editor"
})

if !canPublish.Allow(ctx, user, "publish", nil) {
    return c.JSON(403, map[string]string{"error": "forbidden"})
}