Security & Authentication #
The framework provides a pluggable authentication and authorization layer for the Web server. It supports multiple authentication backends (static, native, access tokens, OAuth) that can be combined, a unified password-login endpoint, and a permission-based authorization system with statically-defined roles.
All security configuration lives under web.security:
web:
security:
enabled: true # master switch — when false, all auth is bypassed
managed: false # true = user/role management is external (read-only here)
authentication: { ... } # identity verification (who you are)
authorization: { ... } # permission checks (what you can do)
Authentication and authorization are configured independently.
authenticationanswers “who is this user?";authorizationanswers “what are they allowed to do?”. A static user list (authentication) is typically paired with static role definitions (authorization).
Authentication #
Configured under web.security.authentication. Multiple backends can be
enabled at once; they are tried in order until one recognizes the credential.
web:
security:
authentication:
native: { enabled: true } # ORM-backed user store
access_token: { enabled: true } # long-lived API tokens (X-API-TOKEN)
http_basic: { enabled: false } # external basic-auth gateway (not yet for web)
oauth: # OAuth providers (google, github, ...)
google: { ... }
static: # inline user list — see below
enabled: true
users: [ ... ]
Static authentication (inline users) #
The static backend lets you declare users directly in the config file (or via
environment-overridden config). It is the simplest way to bootstrap accounts
without an ORM/native user store, and is the default for single-binary
deployments.
web:
security:
authentication:
static:
enabled: true
users:
- id: admin # optional; defaults to `login`
name: Administrator # display name
login: admin@example.com # username or email — the login key
password: "Admin@123" # PLAINTEXT — auto-hashed at startup (see note)
roles: [admin] # role names; resolved by authorization.static
- id: readonly
login: viewer@example.com
password: "Viewer@123"
roles: [viewer]
users[] fields
#
| Field | Required | Description |
|---|---|---|
login | yes | Username or email. The unique login key used at /account/login. |
password | yes* | Plaintext. Automatically bcrypt-hashed (bcrypt.DefaultCost) at startup. |
roles | no | List of role names granted to this user (resolved by authorization). |
id | no | Stable user ID. Defaults to login when omitted. |
name | no | Human-readable display name. |
* A user without password cannot log in via password (password-less service
account).
Password handling — important #
The password value is plaintext in the config. At startup the static
module hashes each one with bcrypt (bcrypt.GenerateFromPassword) and stores
only the hash in memory; the original plaintext is never retained. Login then
verifies with bcrypt.CompareHashAndPassword.
Because the config still carries plaintext, prefer referencing secrets from the keystore rather than committing them:
users:
- login: admin@example.com
password: $[[keystore.admin_password]] # decrypted at load time
roles: [admin]
See Keystore for managing secret values.
Other backends #
native— users/roles persisted via ORM (Elasticsearch). Enables the in-app user-management UI. Pair withauthorization.native.access_token— issues and validates long-lived API tokens sent via theX-API-TOKENheader. Managed throughPOST /auth/access_token(requires login first).native: true(default when native realm is on) persists tokens via ORM;native: falseis KV-only.oauth— external providers (google,github, …). Each entry needs at minimumtype,client_id,client_secret,url.http_basic— delegates basic-auth to an external endpoint. Not yet wired into the web stack; use API basic auth or OAuth instead.
Unified login endpoint — POST /account/login
#
When at least one password-based backend (static or native) is enabled,
the framework registers a shared login endpoint:
POST /account/login
Content-Type: application/json
{"login": "admin@example.com", "password": "Admin@123"}
HTTP/1.1 200 OK
Set-Cookie: session_token=<jwt>; HttpOnly; ...
Content-Type: application/json
{"access_token": "<jwt>", "expire_in": 1786559999, "status": "ok"}
Behavior:
- Looks up the user across all registered backends via
security.GetUserByLogin(static, native, …) and verifies the password with bcrypt. - On success, creates a JWT session (24h) stored in the
session_tokencookie. Subsequent requests are authenticated by the session-token auth filter. - Rate-limited: 10 attempts/minute per client IP. Returns
429when exceeded. - Errors:
400(bad body),401(invalid credentials),429(rate limited).
Route gating: the endpoint is registered only when static.enabled or
native.enabled is true (checked after config load, via
RegisterFuncBeforeSetup). Deployments using only OAuth/access-token never
register /account/login, so they expose no password-bruteforce surface.
Logout: POST /account/logout (or GET) clears the session.
Authorization #
Configured under web.security.authorization. The static provider declares
roles and their permissions inline; a user’s effective permissions are the
union of the permissions of all roles assigned to them (via the authentication
side users[].roles, or role_mapping).
web:
security:
authorization:
static:
enabled: true
roles:
- name: admin
permissions:
- "*" # wildcard — grants everything
- name: viewer
permissions:
- "generic:entity:card:read"
- "logpilot#stream:read"
- "logpilot#pattern:read"
role_mapping: # map a login/subject → role(s)
admin@example.com: [admin]
viewer@example.com: [viewer]
roles[]
#
| Field | Description |
|---|---|
name | Role name. Referenced by users[].roles and role_mapping. |
permissions | List of permission keys. * grants all permissions (superuser). |
role_mapping
#
Optional. Maps a subject (typically a login or external identity) to one or more role names. This is useful when roles come from an external/OAuth identity and you want to translate them to local role names.
Permission keys #
A permission key is an opaque string the application defines and checks via
api.RequirePermission(...). Conventions vary by app — e.g.
"<scope>:<resource>:<action>" or "<scope>#<resource>/<action>". The
framework itself only compares strings; the application registers the
meaningful keys (see
API & Web Framework for how
handlers attach permission requirements).
Complete example #
A typical static-only deployment with login, role-based permissions, and API tokens:
web:
security:
enabled: true
managed: false
authentication:
static:
enabled: true
users:
- id: admin
name: Administrator
login: admin@infini.labs
password: $[[keystore.admin_password]]
roles: [admin]
- id: operator
login: operator@infini.labs
password: $[[keystore.operator_password]]
roles: [operator]
access_token:
enabled: true
authorization:
static:
enabled: true
roles:
- name: admin
permissions: ["*"]
- name: operator
permissions:
- "logpilot#stream/read"
- "logpilot#stream/create"
- "logpilot#pattern/read"
- "logpilot#ai/admin"
role_mapping:
admin@infini.labs: [admin]
operator@infini.labs: [operator]
With this config:
POST /account/loginacceptsadmin@infini.labs/ the keystore password (static backend verifies the bcrypt-hashed value).- The session gets role
admin→ all permissions. operatorcan read/create streams, read patterns, and manage AI config, but cannot delete streams (nologpilot#stream/delete).- Both users can mint API tokens via
POST /auth/access_tokenfor headless/CLI use.
Env-var overrides #
AI/security-relevant secrets can be supplied through the keystore
($[[keystore.xxx]]) or environment variables processed by the app’s config
loader. The static backend reads from the merged config, so any config-source
override (file, env, keystore) is honored — write secrets once in the keystore
and reference them everywhere.