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 — ADMIN or USER. A global admin implicitly owns every project.

  • Project membership — a user can be a project OWNER, WRITE, or READ member. OWNER can manage members and write; WRITE can write (settings, …); READ can 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

unitrack.security.signup-min-password-length

Minimum password length (default 8).

unitrack.security.signup-rate-limit-per-hour

Max signups per client IP per hour (default 10; 0 disables). In-memory, single-instance.

8. Demo accounts

When unitrack.demo.enabled=true, a test/test user is seeded alongside sample projects (one public, two private with the test user as a member), so an evaluation instance shows the public/private split. Never enable the demo seeder on a real deployment.

9. Putting it together

Goal Setting

Fully public internal dashboard

open-mode=true (default)

Public dashboard, protected uploads

open-mode=true, require-ingest-token=true

Locked down — login for everything

open-mode=false