Rework the FreeCAD client authentication to match the Silo backend's
actual auth implementation (local, LDAP, OIDC/Keycloak, API tokens).
Auth model change:
- Remove the old AuthToken/AuthTokenExpiry session-token approach
- Unify on API tokens (ApiToken preference / SILO_API_TOKEN env var)
as the single auth mechanism for the desktop client
- _get_auth_token() now delegates to _get_api_token() directly
- All requests use Bearer token auth via _get_auth_headers()
SiloClient.login() rewrite:
- POST form-encoded credentials to /login (matching the backend's
form-based login handler, works with local and LDAP backends)
- Use the resulting session cookie to call GET /api/auth/me to get
user info (username, role, auth_source)
- Create a persistent API token via POST /api/auth/tokens named
'FreeCAD (hostname)' with 90-day expiry
- Store the raw token in ApiToken preference for all future requests
- No more ephemeral session tokens — API tokens survive restarts
New SiloClient methods:
- get_current_user(): GET /api/auth/me, returns user dict or None
- refresh_auth_info(): fetches /api/auth/me and updates cached prefs
- auth_role(): returns stored role (admin/editor/viewer)
- auth_source(): returns stored auth source (local/ldap/oidc)
- list_tokens(): GET /api/auth/tokens
- create_token(name, expires_in_days): POST /api/auth/tokens
- revoke_token(token_id): DELETE /api/auth/tokens/{id}
New preference keys:
- AuthRole: cached user role from server
- AuthSource: cached auth source (local, ldap, oidc)
Removed preference keys:
- AuthToken: replaced by ApiToken (was duplicative)
- AuthTokenExpiry: API tokens have server-side expiry
Auth helper changes:
- _save_auth_info(): stores username, role, source, and optionally token
- _clear_auth(): clears ApiToken, AuthUsername, AuthRole, AuthSource
- _get_auth_role(), _get_auth_source(): new accessors
Dock widget updates:
- New Role row showing role and auth source (e.g. 'editor (ldap)')
- Status refresh validates token against /api/auth/me on each poll
- Four status states: Connected (green), Token invalid (orange),
Connected no auth (yellow), Disconnected (red)
- Caches validated user info back to preferences
Login dialog updates:
- Info text explains the flow (creates persistent API token)
- Placeholder text updated (removed LDAP-specific wording)
- Shows 'Logging in...' status during auth
- Displays role and auth source on success
- Disables login button during request
Settings dialog updates:
- API Token input field with show/hide toggle
- Token can be pasted directly from Silo web UI
- Hint text explains token sources (web UI, Login, env var)
- 'Clear Token and Logout' button replaces old clear credentials
- Save handler persists token changes
- Status summary shows role and auth source
check_connection() fix:
- Uses origin /health (not /api/health) matching actual route
Fixes the endpoint mismatch where client was calling a non-existent
POST /api/auth/login JSON endpoint. Now uses the actual form-based
POST /login + session cookie flow that the backend implements.
Client-side authentication support for the Silo database API. Adds a
compact dock widget for auth status and login, bearer token management
on all HTTP requests, and an enhanced settings dialog.
Auth helper functions:
- _get_auth_token(): reads token from FreeCAD preferences, checks expiry
- _get_auth_username(): reads stored username from preferences
- _get_auth_headers(): returns Authorization bearer header dict
- _clear_auth(): clears token, username, and expiry from preferences
SiloClient auth methods:
- login(username, password): POSTs to /auth/login, stores token/user/expiry
- logout(): clears stored credentials via _clear_auth()
- is_authenticated(): checks for valid stored token
- auth_username(): returns stored username
- check_connection(): GETs /health to test server reachability
Auth header injection:
- _request(), _download_file(), _upload_file(), delete_bom_entry() all
merge _get_auth_headers() into outgoing HTTP requests
- _request() clears stored token on 401 response
SiloAuthDockWidget:
- Status indicator dot (green=authenticated, yellow=no auth, red=disconnected)
- Displays current username and server URL
- Login/Logout toggle button
- Gear button opens Silo_Settings dialog
- 30-second QTimer polls connection and auth status
- Login dialog with username/password fields and inline error display
Silo_Auth command:
- Shows/focuses the auth dock panel from menu or toolbar
- Registered in workbench toolbar alongside existing commands
Silo_Settings enhancements:
- New Authentication section showing current auth status
- Clear Saved Credentials button
- Active values summary now includes authentication state
New icon:
- silo-auth.svg padlock icon in Catppuccin Mocha style
Graceful degradation: when backend auth endpoint does not yet exist,
login fails with a clear error and all existing unauthenticated
requests continue to work as before (empty auth headers are a no-op).
- Add migration 009: sourcing_type (manufactured/purchased), sourcing_link,
long_description, and standard_cost columns on items table
- Update Item struct, repository queries, and API handlers for new fields
- Add sourcing badge, long description block, standard cost, and sourcing
link display to item detail panel
- Add inline project tag editor in detail panel (add/remove via dropdown)
- Add new fields to create and edit modals
- Update CSV import/export for new columns
- Merge with auth CreatedBy/UpdatedBy changes from stash
Add run_migrations function to deploy.sh that automatically applies
pending SQL migrations during deployment. Migrations are run after
config installation and before service restart.
Migration runner:
- Sources /etc/silo/silod.env for SILO_DB_PASSWORD
- Reads DB host/port/name/user from production config.yaml
- Waits for database connectivity (5 retries)
- Applies each migration file in order, skipping already-applied ones
- Gracefully degrades if psql is missing or DB password is not set
This fixes the missing migration 009 (auth tables) that caused:
- 'column created_by of relation projects does not exist'
- 'relation api_tokens does not exist'
Also adds auth environment variables to silod.env.example:
- SILO_SESSION_SECRET
- SILO_ADMIN_USERNAME / SILO_ADMIN_PASSWORD
- SILO_OIDC_CLIENT_SECRET, SILO_LDAP_BIND_PASSWORD
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
- Silo_ToggleMode: checkable toolbar button that swaps Ctrl+O/S/N
between standard FreeCAD file commands and Silo equivalents
- _swap_shortcuts() helper stores/restores original QAction shortcuts
- SSL settings: add CA certificate file browser (SslCertPath preference)
with QFileDialog for .pem/.crt/.cer, loaded in _get_ssl_context()
- Integrate Silo_BOM into workbench toolbar (after upstream BOM merge)
- Add Silo_ToggleMode to toolbar as first item with separator
Implement the full Bill of Materials stack on top of the existing
relationships table and bom_single_level view from migration 001.
API endpoints (6 new routes under /api/items/{partNumber}/bom):
- GET /bom Single-level BOM for an item
- GET /bom/expanded Multi-level BOM via recursive CTE (depth param)
- GET /bom/where-used Reverse lookup: which parents use this item
- POST /bom Add child to BOM with quantity, ref designators
- PUT /bom/{child} Update relationship type, quantity, ref des
- DELETE /bom/{child} Remove child from BOM
Database layer (internal/db/relationships.go):
- RelationshipRepository with full CRUD operations
- Single-level BOM query joining relationships with items
- Multi-level BOM expansion via recursive CTE (max depth 20)
- Where-used reverse lookup query
- Cycle detection at insert time to prevent circular BOMs
- BOMEntry and BOMTreeEntry types for denormalized query results
Server wiring:
- Added RelationshipRepository to Server struct in handlers.go
- Registered BOM routes in routes.go under /{partNumber} subrouter
FreeCAD workbench (pkg/freecad/silo_commands.py):
- 9 new BOM methods on SiloClient (get, expanded, where-used, add,
update, delete)
- Silo_BOM command class with two-tab dialog:
- BOM tab: table of children with Add/Edit/Remove buttons
- Where Used tab: read-only table of parent assemblies
- Add sub-dialog with fields for part number, type, qty, unit, ref des
- Edit sub-dialog pre-populated with current values
- Remove with confirmation prompt
- silo-bom.svg icon matching existing toolbar style
- Command registered in InitGui.py toolbar
No new migrations required - uses existing relationships table and
bom_single_level view from 001_initial.sql.
Opening documents (especially assemblies) inside the QDialog nested
event loop can crash FreeCAD when the Assembly solver triggers during
document restore. Move FreeCAD.openDocument() to after dialog.exec_()
returns.
Use urllib.parse to check if the URL has a path component. Only append
/api when the path is empty (e.g. https://silo.kindred.internal).
URLs that already include a path (e.g. http://localhost:8080/api) are
left unchanged. Restore localhost placeholder as the default.
- Auto-append /api to base URL if not already present, so users can
enter just the hostname (e.g. https://silo.kindred.internal)
- Load system CA certificate bundle in SSL context so the bundled
Python trusts internal CAs (FreeIPA) without disabling verification
- Update settings dialog placeholder and hint text to clarify expected
URL format
Replace hardcoded FreeCAD addon path searches with __file__-relative
resolution for icons in both InitGui.py and silo_commands.py. Icons now
load correctly regardless of install location.
Add Silo_Settings command with URL and SSL verification fields. Settings
persist via FreeCAD preferences and take priority over env vars. Wire
SSL context into all SiloClient HTTP methods.