Policy Engine#
Tinyauth's policy engine is the central access-control decision system that evaluates every inbound proxy request. It lives in internal/service/policy_engine.go and is invoked by ProxyController.proxyHandler.
Core Concepts#
Effect and Policy#
Each rule returns one of three outcomes :
| Effect | Meaning |
|---|---|
EffectAllow | Explicitly grants access |
EffectDeny | Explicitly denies access |
EffectAbstain | No opinion; falls back to the global policy |
When a rule abstains, effectToAccess resolves the outcome against the configured global policy:
PolicyAllow(default) β abstain β allowPolicyDenyβ abstain β deny
The global policy is set via auth.acls.policy (values: "allow" or "deny", default "allow") and was introduced in PR #852.
ACLContext#
Every rule receives an ACLContext carrying:
ACLsβ the matched*model.App(per-app config)UserContextβ authenticated user (provider, email, groups)IPβ client IP addressPathβ request pathTrustedProxiesConfiguredβ whether trusted proxies are set (affects IP rules)
Rule Evaluation Order#
proxyHandler evaluates rules in strict sequence. Each rule short-circuits on a definitive outcome:
-
RuleIPBypassedβ If the client IP is in the global or per-appip.bypasslist, skip authentication entirely and return 200. Requires trusted proxies to be configured; otherwise always abstains. -
RuleAuthEnabledβ Evaluatespath.allowandpath.blockregex patterns. If the request path matches the allow pattern (and is not blocked), returnsEffectAllow, bypassing authentication for that path. -
RuleIPAllowedβ Merges global and per-appip.block/ip.allowCIDR lists. Block list is checked first; then allow list. If no trusted proxies are configured, always returnsEffectAllowto avoid blocking behind a Docker bridge. -
RuleUserAllowedβ For OAuth users, checks the per-appoauth.whitelist(email filter). For local users, checksusers.blockfirst, thenusers.allow. An empty allow list returnsEffectAbstain. -
RuleOAuthGroup/RuleLDAPGroupβ Applied only to OAuth or LDAP users respectively. If no groups are configured on the app, returnsEffectAllowto avoid breaking deny-policy setups. Otherwise, the user must belong to at least one group in the configured list.
Request
β
ββ IP in bypass list? β 200 (skip auth)
ββ Path bypasses auth? β 200 (skip auth)
ββ IP blocked/not allowed? β 403 /unauthorized
ββ User not allowed? β 403 /unauthorized
ββ Group check fails? β 403 /unauthorized
ββ Unauthenticated? β 401 redirect to /login
OAuth Access Control#
For OAuth users, two rules are relevant:
-
Email whitelist (
RuleUserAllowed):AppOAuth.Whitelistis a comma-separated list of allowed email addresses or glob patterns. A match returnsEffectAllow; no match returnsEffectDeny. If the whitelist is empty,EffectDenyis still returned for OAuth users β unlike local users where an empty allow list abstains β so OAuth users require an explicit whitelist entry. -
Group membership (
RuleOAuthGroup):AppOAuth.Groupsrestricts access to users whose provider-supplied groups include at least one entry matching the configured list. Providers registered inmodel.OverrideProvidersskip the group check entirely.
Key Files#
| File | Purpose |
|---|---|
internal/service/policy_engine.go | PolicyEngine struct, Effect type, ACLContext, Evaluate, effectToAccess |
internal/service/access_controls_rules.go | All rule implementations (RuleUserAllowed, RuleOAuthGroup, RuleLDAPGroup, RuleAuthEnabled, RuleIPAllowed, RuleIPBypassed) |
internal/controller/proxy_controller.go | proxyHandler β the caller that sequences rule evaluation |
internal/model/config.go | ACLsConfig, AppUsers, AppOAuth, AppLDAP config structs |
Configuration Reference#
All ACL fields are comma-separated strings unless noted. Label-based equivalents follow the pattern tinyauth.apps.<name>.<field>.
| Config key | Rule | Effect when empty |
|---|---|---|
auth.acls.policy | global fallback | allow |
apps.<n>.users.allow | RuleUserAllowed | EffectAbstain |
apps.<n>.users.block | RuleUserAllowed | skipped |
apps.<n>.oauth.whitelist | RuleUserAllowed | EffectDeny for OAuth users |
apps.<n>.oauth.groups | RuleOAuthGroup | EffectAllow |
apps.<n>.ldap.groups | RuleLDAPGroup | EffectAllow |
apps.<n>.ip.allow | RuleIPAllowed | EffectAllow |
apps.<n>.ip.block | RuleIPAllowed | skipped |
apps.<n>.ip.bypass | RuleIPBypassed | EffectDeny |
apps.<n>.path.allow (regex) | RuleAuthEnabled | EffectDeny |
apps.<n>.path.block (regex) | RuleAuthEnabled | skipped |