Accounts & API Tokens
UniTrack has a lightweight account system: form login for the dashboard, per-user personal API tokens for automated uploads, and an open mode that keeps the barrier low for internal deployments.
1. Open mode
By default (unitrack.security.open-mode=true) all dashboard and API endpoints are public,
except /profile and /api/v1/me, which always require a signed-in principal. This makes
a fresh deployment immediately browsable. Set open-mode=false to require authentication for
everything.
Independently, unitrack.security.require-ingest-token=true locks down just the ingest
endpoint so that pushing results requires a token, even while the dashboard stays open.
2. The admin user
On first start an admin user is created. Provide its password with
unitrack.security.admin-password; if you omit it, a random password is generated and
written to the logs. If the configured password later changes, the admin password is
re-synced on startup so the two never drift.
3. Personal API tokens
Sign in, open the Profile page, and mint a token. The raw token (prefixed ut_) is shown
once — copy it then. UniTrack stores only its SHA-256 hash plus a short prefix for
display, and records the last-used time. Tokens can carry an expiry and be revoked at any
time.
Use a token from CI:
scripts/unitrack-upload.sh --token "$UNITRACK_TOKEN" --project app --junit '...'
# or, raw:
curl -H "Authorization: Bearer ut_xxx" http://localhost:8080/api/v1/me
Tokens are accepted as Authorization: Bearer <token> or as the X-UniTrack-Token header.
Token scope (least privilege). A token is either Full (acts as you — read, manage, upload)
or Ingest only. An ingest-only token may call POST /api/v1/ingest and nothing else —
presenting it on any other endpoint returns 403. Use ingest-only tokens as CI secrets: if one
leaks it can’t read private projects or change anything, only upload results.
4. Profile management
The Profile page lets a signed-in user edit their display name and email, change their
password, and manage (create / revoke) their API tokens. Global roles are ADMIN and USER.
5. Roles & project membership
Authorization has two layers:
-
Global role —
ADMINorUSER. A global admin implicitly owns every project. -
Project membership — a user can be a project
OWNER,WRITE, orREADmember.OWNERcan manage members and write;WRITEcan write (settings, …);READcan view.
Project writes (e.g. saving settings) require WRITE or above (or
global admin); managing members requires OWNER (or global admin). Owners and admins manage a
project’s members from its Members page (/projects/{id}/members): add a user by username
with a role, change a role, or remove a member. Reads follow the
open-mode setting (public by default, otherwise any signed-in user).
6. Project visibility (public / private)
Each project is PUBLIC or PRIVATE:
-
PUBLIC — readable by anyone (including anonymous users in open mode).
-
PRIVATE — readable only by its members (READ or higher) and global admins. A project a user can’t read returns 404 everywhere — the dashboard board, project/run pages, the REST API, and the MCP tools all hide it (so its existence isn’t leaked).
New projects default to PRIVATE; change the default with
unitrack.security.default-visibility (PRIVATE or PUBLIC). The owner flips a project’s
visibility on its Settings page. Writes (settings, triage rules, flaky status, member
management) always require WRITE/OWNER membership regardless of visibility.
Ingest interaction: uploading to an existing project requires WRITE membership when the
request is authenticated (or when require-ingest-token=true); a brand-new project is created
with the default visibility and the authenticated uploader becomes its OWNER. Anonymous
uploads still work when require-ingest-token=false (open-mode CI), but such auto-created
projects have no owner and are visible only to admins until membership is granted. MCP tools
run without a per-user principal today, so they expose only PUBLIC projects.
7. Self-service signup
Disabled by default. Set unitrack.security.signup-enabled=true to show a Sign up link and
accept new local accounts at /signup; a new account is a regular user and is auto-logged-in.
Usernames and emails must be unique (email verification is not yet implemented). Abuse guards:
| Property | Meaning |
|---|---|
|
Minimum password length (default 8). |
|
Max signups per client IP per hour (default 10; 0 disables). In-memory, single-instance. |