access

package
v0.25.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Can

func Can(ctx context.Context, permission Permission) bool

Can reports whether the request context carries the given permission. It reads the RolePolicy and roles installed via WithPolicy / WithRoles (by access.Middleware or battery/auth). Returns false when no policy is present — the secure-by-default answer for an un-wired request. This is the seam the CRUD layer uses to enforce EntityConfig.Access.

func GetRoles added in v0.3.2

func GetRoles(ctx context.Context) []string

GetRoles reads back the roles installed via WithRoles (by access.Middleware or battery/auth). It is the reader half of the role-context seam — without it, role context is one-way (you can put roles in but not read them out), which blocks role-based UI branching (e.g. "show the admin nav only when the caller holds 'admin'").

Returns nil when ctx is nil or carries no roles — never panics. A nil context is treated as an anonymous request.

func Middleware

func Middleware(policy *RolePolicy, roles func(ctx context.Context) []string) func(http.Handler) http.Handler

Middleware installs the RBAC policy and the request's roles into the context so downstream RequirePermission middleware and auto-CRUD permission gates (EntityConfig.Access) can resolve permissions. roles maps a request context to the caller's roles — typically by reading the authenticated user; pass nil to install only the policy (roles resolved elsewhere). Mount this once, app-wide or on a route group, ahead of any permission-gated routes.

func RequirePermission

func RequirePermission(permission Permission) func(http.Handler) http.Handler

RequirePermission returns HTTP middleware that checks if the current user has the specified permission. Returns 403 if denied.

func WithPolicy

func WithPolicy(ctx context.Context, policy *RolePolicy) context.Context

WithPolicy stores a RolePolicy in the context.

func WithRoles

func WithRoles(ctx context.Context, roles []string) context.Context

WithRoles stores user roles in the context.

Types

type GrantStore added in v0.20.0

type GrantStore struct {
	// contains filtered or unexported fields
}

GrantStore persists role→permission grants to a database table so RBAC edits survive restarts. It wraps a live *RolePolicy: Grant/Revoke write the DB row AND mutate the in-memory policy in one call, keeping the two in sync. The policy's own RWMutex covers concurrent Can checks, so a Grant/Revoke call is "atomic enough" — a reader may see the state before or after the change, never a torn map.

The store holds a reference to the live *RolePolicy (store-holds-policy shape). Bind the policy at construction with NewGrantStore(db, policy), then call LoadInto once at boot to hydrate the policy from persisted rows. Subsequent Grant/Revoke calls mutate both layers.

All role and permission VALUES are passed as $n bound parameters — never interpolated into SQL. The table name is validated via query.SafeIdent at construction time and quoted via query.QuoteIdent in every statement.

Both SQLite (mattn/go-sqlite3) and PostgreSQL (lib/pq) accept $N placeholders and ON CONFLICT DO NOTHING, so the same SQL works on both.

func NewGrantStore added in v0.20.0

func NewGrantStore(db *sql.DB, policy *RolePolicy, opts ...GrantStoreOption) *GrantStore

NewGrantStore creates a GrantStore bound to the given policy. The policy reference is retained — Grant/Revoke mutate it directly so concurrent Can checks see the change without a reload. Call LoadInto once at boot to hydrate the policy from persisted rows.

A nil policy is allowed only if you intend to call LoadInto with a policy before any Grant/Revoke; Grant/Revoke on a store with a nil policy return an error.

func (*GrantStore) EnsureSchema added in v0.20.0

func (s *GrantStore) EnsureSchema(ctx context.Context) error

EnsureSchema creates the grants table if it does not already exist. Idempotent (CREATE TABLE IF NOT EXISTS). The column types (TEXT) are portable across SQLite and PostgreSQL. The (role, permission) pair has a UNIQUE constraint so INSERT ... ON CONFLICT DO NOTHING is a no-op for duplicates.

func (*GrantStore) Grant added in v0.20.0

func (s *GrantStore) Grant(ctx context.Context, role string, perms ...Permission) error

Grant persists (role, permission) rows to the database (INSERT ... ON CONFLICT DO NOTHING) and then calls policy.Grant on the live policy, keeping the DB and the in-memory policy in sync. Idempotent: granting an already-held permission is a no-op in both layers.

Role and permission are bound as $n parameters — never interpolated.

func (*GrantStore) LoadInto added in v0.20.0

func (s *GrantStore) LoadInto(ctx context.Context, policy *RolePolicy) error

LoadInto reads all persisted grant rows and calls policy.Grant for each, hydrating the live *RolePolicy from the database. The policy is also retained as the store's active policy (overwriting any previously bound one) so subsequent Grant/Revoke calls mutate it. Call once at boot, after constructing the policy and after EnsureSchema.

If the store was constructed with a policy and policy is nil, the store's existing policy is used.

func (*GrantStore) Policy added in v0.20.0

func (s *GrantStore) Policy() *RolePolicy

Policy returns the live *RolePolicy the store mutates. May be nil if LoadInto has not yet been called and no policy was passed to NewGrantStore.

func (*GrantStore) Revoke added in v0.20.0

func (s *GrantStore) Revoke(ctx context.Context, role string, perms ...Permission) error

Revoke deletes (role, permission) rows from the database and then calls policy.Revoke on the live policy. Idempotent: revoking a permission the role doesn't hold is a no-op in both layers.

Role and permission are bound as $n parameters — never interpolated.

type GrantStoreOption added in v0.20.0

type GrantStoreOption func(*GrantStore)

GrantStoreOption configures a GrantStore.

func WithGrantTable added in v0.20.0

func WithGrantTable(name string) GrantStoreOption

WithGrantTable overrides the default table name ("access_grants"). The name is validated via query.SafeIdent — an unsafe identifier panics at construction time, not at query time.

type Permission

type Permission string

Permission represents an action permission string (e.g. "posts:read", "posts:write").

const Wildcard Permission = "*"

Wildcard is the superuser permission: a role granted "*" passes every permission check. Grant it deliberately and only to fully-trusted, separately-gated surfaces (e.g. the admin back-office, which has its own Authorize gate) — never to an end-user role.

func GetPermissions

func GetPermissions(ctx context.Context) []Permission

GetPermissions extracts the user's permissions from context by looking up the user's roles against the RolePolicy.

Returns nil if ctx is nil, missing a policy, or missing roles — never panics. A nil context is treated as an anonymous request rather than allowed to crash the handler.

type Policy

type Policy interface {
	Can(ctx context.Context, permission Permission) bool
}

Policy determines whether the subject in ctx holds a permission.

type RolePolicy

type RolePolicy struct {
	// contains filtered or unexported fields
}

RolePolicy implements Policy using role-based permission grants.

Grant and Revoke may be called concurrently with Can / GetPermissions: the underlying role→permissions map is guarded by an RWMutex so reads don't block each other and writes won't trigger Go's concurrent-map fatal.

func NewRolePolicy

func NewRolePolicy() *RolePolicy

NewRolePolicy creates a new empty RolePolicy.

func (*RolePolicy) Can

func (rp *RolePolicy) Can(ctx context.Context, permission Permission) bool

Can checks if the user from ctx has the given permission via any of their roles. A role holding the Wildcard permission ("*") passes any check.

func (*RolePolicy) Grant

func (rp *RolePolicy) Grant(role string, permissions ...Permission)

Grant adds permissions to a role.

func (*RolePolicy) PermissionsOf added in v0.20.0

func (rp *RolePolicy) PermissionsOf(role string) []Permission

PermissionsOf returns a defensive copy of the permissions granted to the given role. Returns nil when the role has no grants. Callers iterate the returned slice without holding the lock so a concurrent Grant/Revoke can't mutate it under them.

func (*RolePolicy) Revoke

func (rp *RolePolicy) Revoke(role string, permissions ...Permission)

Revoke removes specific permissions from a role.

func (*RolePolicy) Roles added in v0.20.0

func (rp *RolePolicy) Roles() []string

Roles returns the sorted list of all roles that currently have at least one granted permission. The slice is a defensive copy — callers can iterate it without holding the lock. Intended for admin UIs that need to enumerate the grant matrix; not used on the hot Can path.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL