Security & Access Control

The security module exposes authentication actors, authorization scopes, policies, and token stores. This page is an API reference with partial authorization recipes. Registry IDs, actors, request metadata, token values, application objects such as user and doc, and callbacks such as show_admin_features come from the surrounding application; the examples are not a complete authentication deployment.

Wippy runs in strict security mode by default. The executable entry must enable security, have an actor and scope, and authorize the exact operations it calls. In particular, construction and scope changes need security.actor.create or security.scope.create; registry lookup needs security.policy.get or security.policy_group.get; token work needs security.token_store.get plus the operation-specific token permission. new_actor, new_scope, scope:with, scope:without, and permission-denied token_store acquisition raise a Lua error instead of returning a structured error. Grant these prerequisites in the entry's security context rather than trying to recover after a denial. See Security Model for configuration.

Loading

local security = require("security")

actor

Return the current security actor from the execution context.

local actor = security.actor()
if actor then
    local id = actor:id()
    local meta = actor:meta()
    -- Use only the fields required for authorization or application logic.
    local role = meta.role
end

Actor metadata can contain identifiers or personal data. Do not log the complete metadata table or copy secrets into it.

Returns: Actor|nil

scope

Return the current security scope from the execution context.

local scope = security.scope()
if scope then
    local policies = scope:policies()
    for _, policy in ipairs(policies) do
        print("Active policy:", policy:id())
    end
end

Returns: Scope|nil

can

Check whether the current context allows an action on a resource.

-- Check read permission
if not security.can("read", "user:" .. user_id) then
    return nil, errors.new("Cannot read user data"):kind(errors.PERMISSION_DENIED)
end

-- Check write permission
if not security.can("write", "order:" .. order_id) then
    return nil, errors.new("Cannot modify order"):kind(errors.PERMISSION_DENIED)
end

-- Check with metadata
local allowed = security.can("delete", "document:" .. doc_id, {
    owner_id = doc.owner_id,
    department = doc.department
})
Parameter Type Description
action string Action to check
resource string Resource identifier
meta table Additional metadata (optional)

Returns: boolean

new_actor

Create an actor with an ID and metadata.

-- Create user actor
local actor = security.new_actor("user:" .. user.id, {
    role = user.role,
    department = user.department,
    email = user.email
})

-- Create service actor
local service_actor = security.new_actor("service:payment-processor", {
    type = "service",
    version = "1.0.0"
})
Parameter Type Description
id string Unique actor identifier
meta table Metadata key-value pairs

Returns: Actor

new_scope

Create a custom scope.

-- Empty scope
local scope = security.new_scope()

-- Scope with policies
local read_policy, read_err = security.policy("app:read-only")
if read_err then
    return nil, read_err
end
local scope = security.new_scope({read_policy})

-- Build scope incrementally
local scope = security.new_scope()
local policy1, policy1_err = security.policy("app:read")
if policy1_err then
    return nil, policy1_err
end
local policy2, policy2_err = security.policy("app:write")
if policy2_err then
    return nil, policy2_err
end
scope = scope:with(policy1):with(policy2)

Each alternative above is an isolated construction pattern. new_scope and scope:with can raise on missing context or permission denial; they do not return nil, error for those checks.

Returns: Scope

policy

Retrieve a policy from the registry.

local policy, err = security.policy("app:admin-access")
if err then
    return nil, err
end

-- Evaluate policy
local result = policy:evaluate(actor, "delete", "user:123")
if result == "allow" then
    -- permitted
elseif result == "deny" then
    -- forbidden
else
    -- undefined, check other policies
end
Parameter Type Description
id string Policy ID "namespace:name"

Returns: Policy, error

named_scope

Retrieve a predefined policy group.

-- Get admin scope
local admin_scope, err = security.named_scope("app:admin")
if err then
    return nil, err
end

-- Use for elevated operations
local result = admin_scope:evaluate(actor, "delete", "user:123")

Loading a scope does not elevate the current execution context. It produces a value for explicit evaluation or for an API that accepts a scope; the caller still needs permission to perform the protected operation.

Parameter Type Description
id string Policy group ID

Returns: Scope, error

token_store

Acquire a token store for managing authentication tokens.

local store, err = security.token_store("app:tokens")
if err then
    return nil, err
end

-- Use store...
return store:close()

The caller owns an acquired token store until close() is called. Close it after the final operation on every checked success or error path; repeated closes are safe. A permission denial during acquisition raises a Lua error, while lookup and resource failures return nil, error.

Parameter Type Description
id string Token store ID "namespace:name"

Returns: TokenStore, error

Actor Methods

Method Returns Description
actor:id() string Actor identifier
actor:meta() table Actor metadata

Scope Methods

with / without

Add or remove policies from scope.

local scope = security.new_scope()

-- Add policy
local write_policy, err = security.policy("app:write")
if err then
    return nil, err
end
scope = scope:with(write_policy)

-- Remove policy
scope = scope:without("app:read-only")

with and without return new immutable scope values and raise when security.scope.create is not allowed for the with or without resource.

evaluate

Evaluate all policies in scope.

local result = scope:evaluate(actor, "read", "document:123")
-- "allow", "deny", or "undefined"

if result ~= "allow" then
    return nil, errors.new("Access denied"):kind(errors.PERMISSION_DENIED)
end

contains

Check whether the scope contains a policy.

if scope:contains("app:admin") then
    show_admin_features()
end

policies

Return all policies in the scope.

local policies = scope:policies()
for _, policy in ipairs(policies) do
    print(policy:id())
end

Returns: Policy[]

Policy Methods

Method Returns Description
policy:id() string Policy identifier
policy:evaluate(actor, action, resource, meta?) string "allow", "deny", or "undefined"

TokenStore Methods

create

Create an authentication token.

local actor = security.new_actor("user:123", {role = "user"})
local scope, scope_err = security.named_scope("app:default")
if scope_err then
    return nil, scope_err
end
local store, store_err = security.token_store("app:tokens")
if store_err then
    return nil, store_err
end

local token, err = store:create(actor, scope, {
    expiration = "24h",  -- or milliseconds
    meta = {
        login_ip = request_ip,
        user_agent = user_agent
    }
})
store:close()
if err then
    return nil, err
end
return token

request_ip and user_agent are application-provided request values. Store only metadata needed for security decisions, apply retention limits, and never log or persist the returned bearer token outside the intended credential store.

Parameter Type Description
actor Actor Actor for the token
scope Scope Permissions scope
options.expiration string/number Duration string or ms
options.meta table Token metadata

Returns: string, error

validate

Validate a token and return its actor and scope.

local actor, scope, err = store:validate(token)
store:close()
if err then
    return nil, errors.new("Invalid token"):kind(errors.PERMISSION_DENIED)
end

Here and below, store is a live owned handle and token is an untrusted bearer credential supplied by the caller. Do not log the token, including on validation or revocation errors.

Returns: Actor, Scope, error

revoke

Invalidate a token.

local ok, err = store:revoke(token)
store:close()
if err then
    return nil, err
end

Returns: boolean, error

close

Release the token store resource.

store:close()

Returns: boolean

Permissions

Security policy evaluation applies to security operations.

Security Actions

Action Resource Description
security.policy.get Policy ID Access policy definitions
security.policy_group.get Group ID Access named scopes
security.scope.create custom, with, without Create custom scopes (new_scope) and add/remove policies (scope:with, scope:without)
security.actor.create Actor ID Create actors
security.token_store.get Store ID Access token stores
security.token.validate Store ID Validate tokens
security.token.create Store ID Create tokens
security.token.revoke Store ID Revoke tokens

See Security Model for policy configuration.

Errors

Condition Kind Retryable
No context errors.INTERNAL no
Empty token store ID errors.INVALID no
Permission denied (policy, named_scope, token create/validate/revoke) errors.INVALID no
Permission denied (new_scope, new_actor, token_store, scope:with/without) raised as a Lua error no
Policy not found errors.INTERNAL no
Token store not found errors.INTERNAL no
Token store closed errors.INTERNAL no
Invalid expiration format errors.INVALID no
Token validation failed errors.INTERNAL no
local store, err = security.token_store("app:tokens")
if err then
    if errors.is(err, errors.INVALID) then
        print("Invalid request:", err:message())
    end
    return nil, err
end
store:close()

See Error Handling for working with errors.

See Also