Users, roles & permissions
This page is the UI identity management and runtime RBAC reference for padas-ui: human authentication to Express only, session-backed authorization via JWT in HTTP-only cookies, and identity persistence in users.json / secret.json under PADAS_UI_HOME/data/security/. The identity boundary for operators is the UI server—the browser authenticates to the UI only; the UI then mediates Core access with per-row service Bearer tokens (see Security — runtime operational security reference). Core does not read users.json, validate JWT cookies, or enforce admin / user—runtime authorization on Core is Bearer-gated for the whole /api/v1/* surface, independent of console identity.
Related: Security · REST API Reference · Runtime configurations · Cores · Monitoring · Troubleshooting & Logs · Glossary · Quickstart: Core + UI · Installation — Linux
Overview
| Concern | Runtime behavior |
|---|---|
| Human authentication | POST /auth/login (authController.js) validates credentials (local users.json or LDAP bind); success issues JWT pair via handleToken.js into access_token / refresh_token cookies. |
| Route-level authorization | authenticateToken (authMiddleware.js) verifies JWT with ACCESS_SECRET from secret.json; authorizeRoles([...]) gates routes.js by req.user.roles. |
| JWT cookie lifecycle | Short-lived access token + refresh path; middleware may reject expired access and depend on client refresh flows documented in the same middleware chain. |
| UI as authorization boundary | All mutating console APIs hit Express first; no UI role is sent to Core as a claim—delegated authority to Core is the stored account_token, not the human’s JWT. |
| Human vs service identity | Human identity = JWT + users.json roles. Service identity = Core service-account.token mirrored in the UI registry—orthogonal layers (Security). |
Initial admin bootstrap and first login
First-run operational flow (server/server.js, config/users.js): on first start after licensing, createUsersFile() writes { "local": [] }; createSecretFile() materializes secret.json (ACCESS_SECRET, REFRESH_SECRET). Until users.local is non-empty, the sign-in surface shows System Not Initialized—from a runtime perspective the UI is unauthenticated for human operators but already exposes bootstrap attack surface: GET /auth/init and POST /auth/init are mounted without authenticateToken.
Why /auth/init matters: it is the only supported path to mint the first users.json row with roles: ['admin']. GET /auth/init returns { "initialized": false } until a user exists; POST /auth/init accepts name, username, password, email and always assigns roles: ['admin']. If any user already exists → 409 Conflict. Operational hardening: after bootstrap, /auth/init should be treated as reachable only on a trusted network (ingress allowlist, reverse proxy ACL, or private admin VLAN)—the handler stays mounted; defense is deployment isolation, not a second code gate.
JWT secret generation implications: secret.json defines signing trust for all future sessions. Loss or rotation of that file without coordinated user re-login invalidates existing JWTs—plan JWT secret rotation as a session invalidation event (see Operational identity management).
users.json operational persistence: bootstrap appends the first local user; subsequent CRUD goes through authenticated admin routes. Filesystem permission expectations: treat PADAS_UI_HOME/data/security/ as sensitive—same ownership model as other operational secrets. Bootstrap recovery / reinstall / migration: restoring an old users.json without matching secret.json breaks verification; copying only secret.json from another host desynchronizes issued tokens vs stored password hashes—treat backup/restore as a single security directory concern.
curl -sS http://UI_HOST:PORT/auth/init
curl -sS -X POST http://UI_HOST:PORT/auth/init \
-H "Content-Type: application/json" \
-d '{"name":"Ops Admin","username":"admin1","password":"YourComplexPass1","email":"admin1@example.com"}'
Finally POST /auth/login sets cookies; further API calls require authenticateToken on routes.js. Then register Cores with each engine’s service-account.token (Cores).
In the UI: session menu, profile, and password
Header menu (role in context)
Runtime session identity is whatever authenticateToken decoded into req.user: username and roles drive the header label and Administrator badge when admin is present. This is JWT-backed UI state, not a second server round-trip per click—the badge is a visualization of the same roles array authorizeRoles consults.

User profile
Profile reflects authorization identity as persisted: Name, Username, E-Mail, Roles from users.json for local users. LDAP-backed users see the same shape after bind—the server still issues a local JWT carrying req.user.roles. This screen does not grant privilege by itself; it mirrors what the authentication middleware already embedded in the token.

Change password
Change password is local-user self-service: modal submits credential update paths in authController.js / user controller stack. Operational behavior: successful update changes stored hash in users.json; existing JWTs may remain valid until expiry—operators should assume password rotation does not instantly revoke all sessions unless secrets or token lists are cleared. Local-user scope limitations: LDAP directory passwords are not managed through this modal; operators use IdP flows.

Roles and Express route enforcement
Runtime RBAC in padas-ui is two role strings only: admin and user, declared per route in server/routes/routes.js. The middleware chain is: authenticateToken (JWT from cookie → verify/decode → attach req.user) then, where declared, authorizeRoles(['admin']) or authorizeRoles(['admin','user']), which compares allowed role names to req.user.roles. On failure authorizeRoles responds 403 Forbidden; missing/invalid JWT is 401 from authenticateToken.
| Surface | admin only | admin + user |
|---|---|---|
| Licenses | verify, list, add, delete | — |
| Users / LDAP / audit logs | list, CRUD | — |
POST /logs | ✓ | — |
| Registry create/update/delete, bulk delete | ✓ | — |
| Registry GET | ✓ | ✓ |
| Management deploy / start / stop / restart | ✓ | ✓ |
Monitoring, captures, POST /test/run | ✓ | ✓ |
Why Core does not enforce UI roles: padas-api has no concept of admin / user for HTTP—runtime authorization there is Bearer (or disabled). The UI is a delegated Core authority client: whoever passes authenticateToken + route RBAC can trigger server-side calls that attach the row’s account_token. UI-server-side mediation means effective authority = min(JWT route access, stored Core token scope).
Implementation notes (userController.js, authController.js):
POST /auth/users(guarded byauthorizeRoles(['admin'])) currently forcesreq.body.roles = ['admin']for API-created users—runtime RBAC today collapses new local users to admin-equivalent in code.- LDAP path: after successful bind,
ldapUser.roles = ['admin']beforehandleToken.jsissues JWT—admin-equivalent LDAP behavior for authorization surface on Express.
Security implications (explicit):
| Topic | Behavior |
|---|---|
| Stored Core tokens | Registry holds account_token per Core; any session that can reach management/registry routes can cause the UI process to call Core with that Bearer. |
| UI compromise | Host-level compromise of padas-ui exposes secret.json, users.json, and DB/registry—effective authority includes all configured Core tokens. |
| Current implementation limitations | No fine-grained resource ABAC on UI; LDAP lacks per-group role mapping in code; API user create cannot mint user-only rows today. |
Multiple local users
Local user persistence: accounts live under users.json → local array entries with username, password hash, roles, profile fields. POST /auth/users (admin) appends users but, as above, forces admin role in the handler—operational account management via API cannot yet express least-privilege user creation without code or manual file edit (unsupported operationally).
Admin / user role model: user can read registry and run deploy/monitor flows per route table; admin additionally manages users, LDAP settings, licenses, audit exports, and destructive registry operations. Shared operational environments: shared admin accounts share audit attribution only as well as the last editor metadata on objects—prefer named operators where policy requires accountability.
RBAC limitations: no row-level UI permissions, no per-Core segregation by role—authorization surface is route-wide. Audit implications: auditMiddleware on mutating verbs logs actor from token context; correlate with Monitoring for incident review.
LDAP directory sign-in
Runtime LDAP auth flow: credential POST /auth/login can target the LDAP branch in authController.js (when UI LDAP is configured). Successful LDAP bind builds a user object, assigns ldapUser.roles = ['admin'], then uses the same handleToken.js path as local users—local JWT issuance after LDAP bind; the browser still only ever holds UI JWT cookies, not directory credentials on subsequent requests.
Role mapping limitations: there is no group-to-role matrix in current code—every LDAP session is admin-equivalent for authorizeRoles. Identity provider trust boundary: the UI server trusts the directory for password verification only; directory outage surfaces as LDAP bind failures at login, not as Core errors.
Operational deployment implications: secure LDAPS/TLS to IdP, lock down users.json and LDAP config backup paths, and document that fallback to local admin may be required during IdP incidents. Admin-equivalent LDAP behavior is a known implementation limitation—do not assume least-privilege directory groups map to user until product changes land.
Runtime identity flow
| Step | Runtime behavior |
|---|---|
| Browser login | Operator submits username / password to POST /auth/login. |
/auth/login | authController.js validates local users.json or LDAP; on success calls token helpers. |
| JWT creation | handleToken.js signs access and refresh with ACCESS_SECRET / REFRESH_SECRET. |
| Cookie persistence | HTTP-only cookies carry tokens; browser stores no Core Bearer for human flows. |
| Request authentication | authenticateToken in authMiddleware.js reads access_token, verifies signature/expiry, attaches req.user. |
| Role authorization | authorizeRoles on routes.js checks req.user.roles against allowed list. |
| Backend API access | Mutating handlers run as the Node process, using DB + registry state. |
| Core API mediation | HTTP clients add Authorization: Bearer <account_token> from the selected Core row—delegated authority, not JWT forwarded to Core. |
Trust boundaries and authorization model
| Boundary | Enforcement |
|---|---|
| Browser ↔ UI | Human authentication + session-backed authorization via JWT cookies + Express middleware only. |
| UI ↔ Core | Service authentication with per-Core Bearer; Core does not enforce UI roles. |
| JWT vs Bearer | JWT = operator session to Node; Bearer = automation/Core client secret—separate trust boundary and rotation. |
| Human identity vs machine identity | users.json / LDAP vs service-account.token / registry account_token. |
| Delegated authority | UI actions that call Core always use server-side stored tokens—effective power is host + DB trust, not browser-held Core secrets. |
| Runtime authorization surface | Express route table + Core /api/v1/* Bearer gate—two stacked surfaces operators must harden (Security). |
Operational identity management
- Password rotation: local password change updates
users.json; plan companion session review because outstanding JWTs may persist until expiry/invalidation paths fire. - Admin account hygiene: avoid long-lived shared
admin; split duties where policy allows onceuser-creation API behavior supports it. - Shared accounts risks: weak audit attribution and password churn ambiguity.
- Bootstrap recovery: re-exposing
/auth/initon a freshusers.localempty install vs409on populated data—never mix prod data dirs with labsecret.jsoncasually. - Session invalidation expectations: clearing
userTokenList/ restart behaviors (if configured) and secret rotation all invalidate or strand sessions—communicate maintenance windows. - JWT secret rotation implications: new
secret.jsoninvalidates all issued JWTs—operators must re-login every human session. - UI migration considerations: move
PADAS_UI_HOME/data/security/atomically with the registry DB backingroutes.jsconsumers expect. - Backup sensitivity:
users.json(hashes),secret.json(signing keys), and DB hold operational identity—encrypt backups and restrict restore RBAC.
Security considerations and troubleshooting
| Symptom | Likely cause |
|---|---|
| Expired JWT | 401 from authenticateToken; refresh path or re-login required. |
| Cookie / session issues | Browser blocked cookies, wrong UI origin, or refresh token mismatch leading to token list removal in middleware paths. |
| Invalid role errors | 403 from authorizeRoles—JWT lacks required admin / user entry. |
| LDAP bind failures | Directory unreachable, bad bind DN/password template, or TLS to IdP failing—check UI LDAP config and network. |
Bootstrap conflicts (409) | POST /auth/init when users.local already has a user—expected guard. |
Corrupted users.json | JSON parse failures or manual edit errors—restore from backup; validate file permissions. |
Lost secret.json | Cannot verify any JWT; treat as crypto outage—restore secrets backup or regenerate and force global re-login. |
| Auth redirect loops | Client repeatedly hitting protected routes without valid cookie—check reverse proxy cookie forwarding. |
| Stale sessions after role changes | JWT still carries old roles until re-issue—user must re-login or wait for access token expiry depending on middleware behavior. |
Deeper triage: Troubleshooting & Logs.
Related reading
- Security — trust boundaries, TLS, Core Bearer, token lifecycle, runtime API exposure
- REST API Reference — service authentication to
/api/v1/* - Glossary — JWT session, Bearer authentication, service account token
- Runtime configurations —
PADAS_UI_HOME, engine connectivity context - Cores — Core registration and
account_tokenlifecycle - Monitoring — correlating auth errors with pipeline health
- Troubleshooting & Logs — session, TLS, and reachability playbooks