Wholesale copy of all Kindred Create additions that don't conflict with upstream FreeCAD code: - kindred-icons/ (1444 Catppuccin Mocha SVG icon overrides) - src/Mod/Create/ (Kindred Create workbench) - src/Gui/ Kindred source files (FileOrigin, OriginManager, OriginSelectorWidget, CommandOrigin, BreadcrumbToolBar, EditingContext) - src/Gui/Icons/ (Kindred branding and silo icons) - src/Gui/PreferencePacks/KindredCreate/ - src/Gui/Stylesheets/ (KindredCreate.qss, images_dark-light/) - package/ (rattler-build recipe) - docs/ (architecture, guides, specifications) - .gitea/ (CI workflows, issue templates) - mods/silo, mods/ztools submodules - .gitmodules (Kindred submodule URLs) - resources/ (kindred-create.desktop, kindred-create.xml) - banner-logo-light.png, CONTRIBUTING.md
6.0 KiB
Silo Auth Middleware
Middleware Chain
Every request passes through this middleware stack in order:
RequestID Assigns X-Request-ID header
|
RealIP Extracts client IP from X-Forwarded-For
|
RequestLogger Logs method, path, status, duration (zerolog)
|
Recoverer Catches panics, logs stack trace, returns 500
|
CORS Validates origin, sets Access-Control headers
|
SessionLoadAndSave Loads session from PostgreSQL, saves on response
|
[route group middleware applied per-group below]
Route Groups
Public (no auth)
GET /health
GET /ready
GET /login
POST /login
POST /logout
GET /auth/oidc
GET /auth/callback
No authentication or CSRF middleware. The login form includes a CSRF hidden field but nosurf is not enforced on these routes (they are outside the CSRF-protected group).
Web UI (auth + CSRF)
RequireAuth -> CSRFProtect -> Handler
GET / Items page
GET /projects Projects page
GET /schemas Schemas page
GET /settings Settings page (account info, tokens)
POST /settings/tokens Create API token (form)
POST /settings/tokens/{id}/revoke Revoke token (form)
Both RequireAuth and CSRFProtect are applied. Form submissions must include a csrf_token hidden field with the value from nosurf.Token(r).
API (auth, no CSRF)
RequireAuth -> [RequireRole where needed] -> Handler
GET /api/auth/me Current user info (viewer)
GET /api/auth/tokens List tokens (viewer)
POST /api/auth/tokens Create token (viewer)
DELETE /api/auth/tokens/{id} Revoke token (viewer)
GET /api/schemas/* Read schemas (viewer)
POST /api/schemas/*/segments/* Modify schema values (editor)
GET /api/projects/* Read projects (viewer)
POST /api/projects Create project (editor)
PUT /api/projects/{code} Update project (editor)
DELETE /api/projects/{code} Delete project (editor)
GET /api/items/* Read items (viewer)
POST /api/items Create item (editor)
PUT /api/items/{partNumber} Update item (editor)
DELETE /api/items/{partNumber} Delete item (editor)
POST /api/items/{partNumber}/file Upload file (editor)
POST /api/items/import CSV import (editor)
GET /api/items/{partNumber}/bom/* Read BOM (viewer)
POST /api/items/{partNumber}/bom Add BOM entry (editor)
PUT /api/items/{partNumber}/bom/* Update BOM entry (editor)
DELETE /api/items/{partNumber}/bom/* Delete BOM entry (editor)
POST /api/generate-part-number Generate PN (editor)
GET /api/integrations/odoo/* Read Odoo config (viewer)
PUT /api/integrations/odoo/* Modify Odoo config (editor)
POST /api/integrations/odoo/* Odoo sync operations (editor)
API routes are exempt from CSRF (they use Bearer token auth). CORS credentials are allowed so browser-based API clients with session cookies work.
RequireAuth
internal/api/middleware.go
Authentication check order:
- Auth disabled? Inject synthetic dev user (
adminrole) and continue - Bearer token? Extract from
Authorization: Bearer silo_...header, validate viaauth.Service.ValidateToken(). On success, inject user into context - Session cookie? Read
user_idfromscssession, look up user viaauth.Service.GetUserByID(). On success, inject user into context. On stale session (user not found), destroy session - None of the above?
- API requests (
/api/*): Return401 UnauthorizedJSON - Web requests: Redirect to
/login?next=<current-path>
- API requests (
RequireRole
internal/api/middleware.go
Applied as per-group middleware on routes that require a minimum role:
r.Use(server.RequireRole(auth.RoleEditor))
Checks auth.RoleSatisfies(user.Role, minimum) against the hierarchy admin > editor > viewer. Returns:
401 Unauthorizedif no user in context (should not happen after RequireAuth)403 Forbiddenwith message"Insufficient permissions: requires <role> role"if role is too low
CSRFProtect
internal/api/middleware.go
Wraps the justinas/nosurf library:
- Cookie:
csrf_token, HttpOnly, SameSite=Lax, Secure when auth enabled - Exempt paths:
/api/*,/health,/ready - Form field name:
csrf_token - Failure: Returns
403 Forbiddenwith"CSRF token validation failed"
Templates inject the token via {{.CSRFToken}} which is populated from nosurf.Token(r).
CORS Configuration
Configured in internal/api/routes.go:
| Setting | Auth Disabled | Auth Enabled |
|---|---|---|
| Allowed Origins | * |
From auth.cors.allowed_origins config |
| Allow Credentials | false |
true (needed for session cookies) |
| Allowed Methods | GET, POST, PUT, PATCH, DELETE, OPTIONS | Same |
| Allowed Headers | Accept, Authorization, Content-Type, X-CSRF-Token, X-Request-ID | Same |
| Max Age | 300 seconds | 300 seconds |
FreeCAD uses direct HTTP (not browser), so CORS does not affect it. Browser-based tools on other origins need their origin in the allowed list.
Request Flow Examples
Browser Login
GET /projects
-> RequestLogger
-> CORS (pass)
-> Session (loads empty session)
-> RequireAuth: no token, no session user_id
-> Redirect 303 /login?next=/projects
POST /login (username=alice, password=...)
-> RequestLogger
-> CORS (pass)
-> Session (loads)
-> HandleLogin: auth.Authenticate(ctx, "alice", password)
-> Session: put user_id, renew token
-> Redirect 303 /projects
GET /projects
-> Session (loads, has user_id)
-> RequireAuth: session -> GetUserByID -> inject alice
-> CSRFProtect: GET request, passes
-> HandleProjectsPage: renders with alice's info
API Token Request
GET /api/items
Authorization: Bearer silo_a1b2c3d4...
-> RequireAuth: Bearer token found
-> ValidateToken: SHA-256 hash lookup, check expiry, check user active
-> Inject user into context
-> HandleListItems: returns JSON