Add a complete authentication and authorization system to Silo with three pluggable backends (local bcrypt, LDAP/FreeIPA, OIDC/Keycloak), session management, API token support, and role-based access control. Authentication backends: - Local: bcrypt (cost 12) password verification against users table - LDAP: FreeIPA simple bind with group-to-role mapping - OIDC: Keycloak redirect flow with realm role mapping - Backends are tried in order; users upserted to DB on first login Session and token management: - PostgreSQL-backed sessions via alexedwards/scs + pgxstore - Opaque API tokens (silo_ prefix, SHA-256 hashed, shown once) - 24h session lifetime, HttpOnly/SameSite=Lax/Secure cookies Role-based access control (admin > editor > viewer): - RequireAuth middleware: Bearer token -> session -> redirect/401 - RequireRole middleware: per-route-group minimum role enforcement - CSRF protection via justinas/nosurf on web forms, API exempt - CORS locked to configured origins when auth enabled Route restructuring: - Public: /health, /ready, /login, /auth/oidc, /auth/callback - Web (auth + CSRF): /, /projects, /schemas, /settings - API read (viewer): GET /api/** - API write (editor): POST/PUT/PATCH/DELETE /api/** User context wiring: - created_by/updated_by columns on items, projects, relationships - All create/update handlers populate tracking fields from context - CSV and BOM import handlers pass authenticated username - Revision creation tracks user across all code paths Default admin account: - Configurable via auth.local.default_admin_username/password - Env var overrides: SILO_ADMIN_USERNAME, SILO_ADMIN_PASSWORD - Idempotent: created on first startup, skipped if exists CLI and FreeCAD plugin: - silo token create/list/revoke subcommands (HTTP API client) - FreeCAD SiloClient sends Bearer token on all requests - Token read from ApiToken preference or SILO_API_TOKEN env var Web UI: - Login page (Catppuccin Mocha themed, OIDC button conditional) - Settings page with account info and API token management - User display name, role badge, and logout button in header - One-time token display banner with copy-to-clipboard Database (migration 009): - users table with role, auth_source, oidc_subject, password_hash - api_tokens table with SHA-256 hash, prefix, expiry, scopes - sessions table (scs pgxstore schema) - audit_log table (schema ready for future use) - created_by/updated_by ALTER on items, relationships, projects New dependencies: scs/v2, scs/pgxstore, go-oidc/v3, go-ldap/v3, justinas/nosurf, golang.org/x/oauth2
7.7 KiB
Silo Authentication User Guide
Logging In
Username and Password
Navigate to the Silo web UI. If authentication is enabled, you'll be redirected to the login page.
Enter your username and password. This works for both local accounts and LDAP/FreeIPA accounts — Silo tries local authentication first, then LDAP if configured.
Keycloak / OIDC
If your deployment has OIDC enabled, the login page will show a "Sign in with Keycloak" button. Click it to be redirected to your identity provider. After authenticating there, you'll be redirected back to Silo with a session.
Roles
Your role determines what you can do in Silo:
| Role | Permissions |
|---|---|
| viewer | Read all items, projects, schemas, BOMs. Manage own API tokens. |
| editor | Everything a viewer can do, plus: create/update/delete items, upload files, manage BOMs, import CSV, generate part numbers. |
| admin | Everything an editor can do, plus: user management (future), configuration changes. |
Your role is shown as a badge next to your name in the header. For LDAP and OIDC users, the role is determined by group membership or token claims and re-evaluated on each login.
API Tokens
API tokens allow the FreeCAD plugin, scripts, and CI pipelines to authenticate with Silo without a browser session. Tokens inherit your role.
Creating a Token (Web UI)
- Click Settings in the navigation bar
- Under API Tokens, enter a name (e.g., "FreeCAD workstation") and click Create Token
- The raw token is displayed once — copy it immediately
- Store the token securely. It cannot be shown again.
Creating a Token (CLI)
export SILO_API_URL=https://silo.kindred.internal
export SILO_API_TOKEN=silo_<your-existing-token>
silo token create --name "CI pipeline"
Output:
Token created: CI pipeline
API Token: silo_a1b2c3d4e5f6...
Save this token — it will not be shown again.
Listing Tokens
silo token list
Revoking a Token
Via the web UI settings page (click Revoke next to the token), or via CLI:
silo token revoke <token-id>
Revocation is immediate. Any in-flight requests using the token will fail.
FreeCAD Plugin Configuration
The FreeCAD plugin reads the API token from two sources (checked in order):
- FreeCAD Preferences:
Tools > Edit parameters > BaseApp/Preferences/Mod/Silo > ApiToken - Environment variable:
SILO_API_TOKEN
To set the token in FreeCAD preferences:
- Open FreeCAD
- Go to
Edit > Preferences > General > Macroor use the parameter editor - Navigate to
BaseApp/Preferences/Mod/Silo - Set
ApiTokento your token string (e.g.,silo_a1b2c3d4...)
Or set the environment variable before launching FreeCAD:
export SILO_API_TOKEN=silo_a1b2c3d4...
freecad
The API URL is configured the same way via the ApiUrl preference or SILO_API_URL environment variable.
Default Admin Account
On first deployment, configure a default admin account to bootstrap access:
# config.yaml
auth:
enabled: true
local:
enabled: true
default_admin_username: "admin"
default_admin_password: "" # Set via SILO_ADMIN_PASSWORD env var
export SILO_ADMIN_PASSWORD=<strong-password>
The admin account is created on the first startup if it doesn't already exist. Subsequent startups skip creation. Change the password after first login via the database or a future admin UI.
Configuration Reference
Minimal (Local Auth Only)
auth:
enabled: true
session_secret: "" # Set via SILO_SESSION_SECRET
local:
enabled: true
default_admin_username: "admin"
default_admin_password: "" # Set via SILO_ADMIN_PASSWORD
LDAP / FreeIPA
auth:
enabled: true
session_secret: ""
local:
enabled: true
default_admin_username: "admin"
default_admin_password: ""
ldap:
enabled: true
url: "ldaps://ipa.kindred.internal"
base_dn: "dc=kindred,dc=internal"
user_search_dn: "cn=users,cn=accounts,dc=kindred,dc=internal"
user_attr: "uid"
email_attr: "mail"
display_attr: "displayName"
group_attr: "memberOf"
role_mapping:
admin:
- "cn=silo-admins,cn=groups,cn=accounts,dc=kindred,dc=internal"
editor:
- "cn=silo-users,cn=groups,cn=accounts,dc=kindred,dc=internal"
- "cn=engineers,cn=groups,cn=accounts,dc=kindred,dc=internal"
viewer:
- "cn=silo-viewers,cn=groups,cn=accounts,dc=kindred,dc=internal"
tls_skip_verify: false
OIDC / Keycloak
auth:
enabled: true
session_secret: ""
local:
enabled: true
oidc:
enabled: true
issuer_url: "https://keycloak.kindred.internal/realms/silo"
client_id: "silo"
client_secret: "" # Set via SILO_OIDC_CLIENT_SECRET
redirect_url: "https://silo.kindred.internal/auth/callback"
scopes: ["openid", "profile", "email"]
admin_role: "silo-admin"
editor_role: "silo-editor"
default_role: "viewer"
CORS (Production)
auth:
cors:
allowed_origins:
- "https://silo.kindred.internal"
Environment Variables
| Variable | Description | Config Path |
|---|---|---|
SILO_SESSION_SECRET |
Session encryption key | auth.session_secret |
SILO_ADMIN_USERNAME |
Default admin username | auth.local.default_admin_username |
SILO_ADMIN_PASSWORD |
Default admin password | auth.local.default_admin_password |
SILO_OIDC_CLIENT_SECRET |
OIDC client secret | auth.oidc.client_secret |
SILO_LDAP_BIND_PASSWORD |
LDAP service account password | auth.ldap.bind_password |
SILO_API_URL |
API base URL (CLI and FreeCAD) | — |
SILO_API_TOKEN |
API token (CLI and FreeCAD) | — |
Environment variables override config file values.
Troubleshooting
"Authentication required" on every request
- Verify
auth.enabled: truein your config - Check that the
sessionstable exists in PostgreSQL (migration 009) - Ensure
SILO_SESSION_SECRETis set (empty string is allowed for dev but not recommended) - Check browser cookies —
silo_sessionshould be present after login
API token returns 401
- Tokens are case-sensitive. Ensure no trailing whitespace
- Check token expiry with
silo token list - Verify the user account is still active
- Ensure the
Authorizationheader format is exactlyBearer silo_<hex>
LDAP login fails
- Check
ldaps://URL is reachable from the Silo server - Verify
base_dnanduser_search_dnmatch your FreeIPA tree - Test with
ldapsearchfrom the command line first - Set
tls_skip_verify: truetemporarily to rule out certificate issues - Check Silo logs for the specific LDAP error message
OIDC redirect loops
- Verify
redirect_urlmatches the Keycloak client configuration exactly - Check that
issuer_urlis reachable from the Silo server - Ensure the Keycloak client has the correct redirect URI registered
- Check for clock skew between Silo and Keycloak servers (JWT validation is time-sensitive)
Locked out (no admin account)
If you've lost access to all admin accounts:
- Set
auth.local.default_admin_passwordto a new password viaSILO_ADMIN_PASSWORD - Use a different username (e.g.,
default_admin_username: "recovery-admin") - Restart Silo — the new account will be created
- Log in and fix the original accounts
Or directly update the database:
-- Reset a local user's password (generate bcrypt hash externally)
UPDATE users SET password_hash = '<bcrypt-hash>', is_active = true WHERE username = 'admin';
FreeCAD plugin gets 401
- Verify the token is set in FreeCAD preferences or
SILO_API_TOKEN - Check the API URL points to the correct server
- Test with curl:
curl -H "Authorization: Bearer silo_..." https://silo.kindred.internal/api/items