-
-
Notifications
You must be signed in to change notification settings - Fork 0
Security
Edge authentication + per-endpoint authorization, toggleable so the stack runs locally without a realm (build.md §4.8, §12).
With openwcs.security.enabled=true, the gateway is an OAuth2 resource server: it validates
the JWT against the Keycloak realm, requires auth on /api/**, and forwards the identity
downstream as X-Auth-User / X-Auth-Roles. It always strips client-supplied
versions of those headers (anti-spoofing) — the gateway is the trust boundary. Off by default,
all traffic is permitted so the stack runs without tokens.
This path is exercised end-to-end in CI (GatewayAuthEndToEndTest) against a live Keycloak
Testcontainer that imports the openwcs realm: no token → 401, a realm JWT → 200 with the
identity propagated, and a client-supplied X-Auth-* header is stripped.
-
libs/commoncarries a code-definedPermissioncatalog and aRoleCatalog(ADMIN/SUPERVISOR/OPERATOR/VIEWER) mirroring the IAM seed. - Each service enforces a coded
Permissionagainst the forwardedX-Auth-Roles, gated byopenwcs.security.enabled(no-op when off; 403 problem+json on a missing permission when on). order-management uses anAccessGuard; other services use anRbacFilter. -
Inter-service identity propagation: services forward
X-Auth-*on outbound calls (e.g. allocation → inventory), so downstream RBAC authorizes against the original user.
Separate from the coded Permission RBAC above, each screen in the SPA has a configurable
access level set on the Access control screen (Administration). The level is one of:
- Off — the screen is hidden (not in the nav, route blocked).
- Read — the screen opens view-only; create/edit/delete/save controls are hidden or disabled.
- Write — full access.
It is configured per role (a 3-way Off/Read/Write control) and per individual user (a
Read/Write level on the allow-list). A user's effective level is the strongest of their per-user
entry and any of their roles; ADMIN always has write and can never lock itself out. The UI catalog
(ui/src/auth/screens.ts) holds each screen's built-in default level per role — VIEWER and the
read-only Reporting section default to read, everything else to write — which applies when a screen
has no override.
Storage + API: iam persists only the overrides (screen_access + screen_access_role /
screen_access_user, each with an access_level of READ/WRITE; absence = OFF). GET/PUT /api/iam/screen-access read/replace the full map ({ screenKey: { roles: {role: level}, users: {user: level} } }); GET /api/iam/screen-access/me returns the caller's effective levels.
Gateway enforcement. READ is not just a UI nicety: the gateway rejects a non-admin write
(POST/PUT/PATCH/DELETE) to a screen-owned API path with 403 unless the user's effective level
is WRITE. ScreenWriteCatalog maps the cleanly-owned write surfaces (master-data CRUD, counting,
slotting, /api/flow/*/topology, warehouse-access, screen-access) and mirrors each screen's default
levels; ScreenAccessResolver fetches the override map from iam's network-only
/internal/screen-access (cached ~30 s). Reads always pass, writes to unmapped paths pass
(fail-open), an IAM blip skips the check (fail-open), and admins bypass. Orders (shared
/api/orders), settings (writes fan across many services) and Keycloak-admin user management are
intentionally unmapped and rely on the UI's write-gating; gateway coverage extends incrementally.
The database console (/api/master-data/admin/db/**) is an access-required route: it is
reachable on any method only if the caller has at least READ on the admin-database screen (the
console is SELECT-only, so READ is enough). The console's controller no longer hard-requires ADMIN
— an admin can grant the screen to another role or user via Access control and they can then run
read-only queries, while the SELECT-only validator + read-only transaction still bound what runs.
iam (port 8087) owns the authorization model: users → roles → coded permissions, with
effective-permission resolution (union across roles). Keycloak does authentication; IAM
layers RBAC on top.
A user can set a new password from the login screen without an admin, and crucially rescue
an account that "is not fully set up". A temporary or forced password adds Keycloak's
UPDATE_PASSWORD required action, so the password grant returns invalid_grant ("Account is not
fully set up") and the user can never obtain a token to change the password in the app. The login
screen therefore offers a Change password form, and switches to it automatically when a
sign-in reports that state, prefilling the entered password as the current one.
-
Endpoint:
POST /api/iam/change-password— the one unauthenticated/api/**route (explicitly permitted on the gateway). Identity is proven by the current password:KeycloakClient.verifyPassworddoes a direct grant and treats both a 2xx response and the "not fully set up" error as proof the credential is correct. -
Setting the new password is done via the Keycloak admin API using a dedicated confidential
service-account client
openwcs-iam(least privilege:manage-users/view-users/query-users, secret inOPENWCS_IAM_CLIENT_SECRET). The new password is set permanent and the required actions are cleared. - Rules: new password ≥ 8 characters, must differ from the current one, username non-blank. A wrong current password or a missing user both return a generic 401 (no account enumeration).
- Footgun fix: the admin Set password dialog now defaults Temporary off (still available), so admins do not create the locked state by accident.
Each user is mapped to the warehouses they may work in and one default (iam.user_warehouse;
a partial-unique index enforces at-most-one default per user). Endpoints under
/api/iam/warehouse-access:
-
GET /me— the signed-in user's allowed warehouses + default (the UI's top-bar switcher auto-selects the default on login and scopes every warehouse-related screen; users never type a UUID). -
GET/PUT /{username}— list/replace a user's mapping. ADMIN-only, enforced server-side onX-Auth-Roles(not just in the UI). The default must be one of the allowed warehouses.
A network-only /internal/warehouse-access/{username} (mapped off /api/**, so unreachable
through nginx or the gateway's public routes) lets the gateway resolve a user's warehouses.
Gateway enforcement. For non-admins the gateway resolves the allowed set from IAM (short-TTL
cache; fails open if IAM is unavailable), forwards it downstream as X-Auth-Warehouses, and
rejects with 403 any request that names a warehouseId (query parameter, or the
/warehouses/{id} path) outside that set. Admins are never warehouse-scoped. Writes that carry the
warehouse only in a JSON body are guarded per-endpoint downstream via the forwarded header
(follow-up).
Docker Compose imports platform/keycloak/openwcs-realm.json — realm openwcs with roles
ADMIN/SUPERVISOR/OPERATOR/VIEWER, the public openwcs-web client (direct-access grants on for
dev), the confidential openwcs-iam service-account client (manage-users, used for self-service
password change), and demo users (dev-only passwords). Reached at http://localhost:8180.
Set OPENWCS_SECURITY_ENABLED=true and point the gateway at the realm, e.g.
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8180/realms/openwcs.
Not yet: mTLS between services (internal trust currently rides on forwarded headers behind the edge); custom (non-seed) IAM roles would need a runtime IAM lookup.
openWCS — open-source Warehouse Control System · summarized from build.md & docs/AS-BUILT.md (the repo docs are authoritative).
Design
Flows
- Areas
- Inbound and Inventory
- Slotting and Replenishment
- Goods-to-Person Stations
- Outbound Flow
- Equipment Integration
- Transport Overview
- Process Designer
- Mobile Process Designer
- Hardware Visualisation
- Host Integration
Reporting & Dashboards
Operations