Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ff6c778
test(web): add vitest and Testing Library toolchain
TartanLeGrand Sep 5, 2026
e78d32c
feat(web): add typed auth API client
TartanLeGrand Sep 5, 2026
580c383
feat(web): send session cookies and surface 401/403 as auth events
TartanLeGrand Sep 5, 2026
53f3490
feat(web): add AuthProvider with session loading and 401/403 handling
TartanLeGrand Sep 5, 2026
42c4871
feat(web): add permission guards for routes and actions
TartanLeGrand Sep 5, 2026
d19935a
feat(web): add login page
TartanLeGrand Sep 5, 2026
01938ae
feat(web): add change password page
TartanLeGrand Sep 5, 2026
d90db29
feat(web): permission-aware navigation and user menu
TartanLeGrand Sep 5, 2026
c1c46b4
feat(web): wire auth provider, login route and permission guards
TartanLeGrand Sep 5, 2026
372559d
feat(web): hide write actions without the matching permission
TartanLeGrand Sep 5, 2026
d78b09c
feat(web): add users administration page
TartanLeGrand Sep 5, 2026
8abb781
feat(web): add teams administration page
TartanLeGrand Sep 5, 2026
bb8623a
feat(web): add API keys administration page
TartanLeGrand Sep 5, 2026
070722a
ci: lint, test and build the web UI on pull requests
TartanLeGrand Sep 5, 2026
4bb92cc
ci: exclude LockActions.tsx from the web type check filter
TartanLeGrand Sep 5, 2026
a1f471d
fix(web): refresh auth context before forced password change redirect
TartanLeGrand Sep 5, 2026
7261a78
fix(web): make the login page's authenticated-redirect honor forced p…
TartanLeGrand Sep 5, 2026
d0d2d86
fix(web): keep the session state consistent when sign-out fails
TartanLeGrand Sep 5, 2026
8b67b6a
test(web): cover forced password change ordering on the login page
TartanLeGrand Sep 5, 2026
76a712b
ci: fail the web type check when tsc does not run
TartanLeGrand Sep 5, 2026
4649d15
fix(web): tighten admin dialogs and cache handling
TartanLeGrand Sep 5, 2026
d527382
fix(web): disable local sign-in in static mode
TartanLeGrand Sep 5, 2026
af70264
fix(web): keep the routed tree hidden while the sign-out navigation i…
TartanLeGrand Sep 5, 2026
720164f
test(web): drop the react-router v6 future flags from test routers
TartanLeGrand Sep 28, 2026
93b9cd2
ci: bump checkout and setup-node to v7 in the web workflow
TartanLeGrand Sep 28, 2026
7ac94a1
fix(web): use the Tailwind 4 utility names in the auth components
TartanLeGrand Oct 2, 2026
d49ca74
docs: describe the SSO button and the web test runner
TartanLeGrand Oct 2, 2026
e36de2c
fix(web): keep the scope of a team when editing it
TartanLeGrand Oct 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/web.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Web

on:
pull_request:
paths:
- .github/workflows/web.yml
- web/**

permissions:
contents: read

jobs:
web:
name: Lint, test and build
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- name: Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: npm
cache-dependency-path: web/package-lock.json

- name: Install dependencies
run: npm ci

- name: Lint auth modules
run: npm run lint:auth

- name: Type check auth modules
# The web tree carries pre-existing type errors outside the auth
# modules; the full log is kept for visibility and only errors in
# the files owned by the auth work fail the job. LockActions.tsx is
# excluded from the filter below: it carries a pre-existing TS2322
# on main (line 39) that predates and is unrelated to the auth work.
run: |
set -o pipefail
# tsc exits 2 when it finds type errors: that is a normal outcome
# here, handled by the grep gate below. Disable -e around the
# pipeline itself so a real failure (missing binary, broken
# tsconfig, OOM) does not abort the script before PIPESTATUS is read.
set +e
npx tsc --noEmit --pretty false 2>&1 | tee tsc.log
status=${PIPESTATUS[0]}
set -e
if [ "$status" -ne 0 ] && [ "$status" -ne 2 ]; then
echo "tsc failed to run (exit $status)" >&2
exit 1
fi
if grep -E '^src/(types/auth\.ts|lib/authApi(\.test)?\.ts|lib/authEvents(\.test)?\.ts|lib/api\.ts|contexts/AuthContext(\.test)?\.tsx|components/auth/[A-Za-z.]+\.tsx|components/admin/[A-Za-z.]+\.tsx|components/Toast\.tsx|components/Layout\.tsx|components/DemoBanner\.tsx|pages/Login(\.test)?\.tsx|pages/account/[A-Za-z.]+\.tsx|pages/admin/[A-Za-z.]+\.tsx|pages/Links\.tsx|pages/Locks\.tsx|App(\.test)?\.tsx|test/[A-Za-z.]+\.tsx?)\(' tsc.log; then
echo "type errors in auth modules" >&2
exit 1
fi
echo "auth modules type check clean ($(grep -c 'error TS' tsc.log || true) pre-existing errors elsewhere)"

- name: Unit tests
run: npm test

- name: Build
run: npm run build
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ task generate # buf dep update + buf generate (regenerate gen
npm run dev # Vite dev server on :3000, proxies /api to VITE_BACKEND_URL (default http://localhost:8080)
npm run lint # eslint, --max-warnings 0
npm run build:check # tsc && vite build (type check)
npm test # vitest run (auth modules)
npm run build # production build into web/dist
npm run build:static # demo build, VITE_STATIC_MODE=true

Expand All @@ -29,7 +30,7 @@ task build:all # frontend then Go binary into bin/
docker run -d -p 27017:27017 --name tracker-mongo mongo:7 # local DB
```

There is **no frontend test runner configured** — `web/src/**/__tests__/` directories exist but are empty and no vitest/jest dependency is installed. Do not invent `npm test`. Load tests live in `tests/k6/` (`task k6:generate`, `task k6:test-locks`).
Frontend tests run with vitest and Testing Library: `npm test` from `web/` (`npm run test:watch` to watch, `npm run lint:auth` for the lint-clean auth modules). The suite covers the auth modules; most other pages have no tests yet. Load tests live in `tests/k6/` (`task k6:generate`, `task k6:test-locks`).

Note: `task dev:all` prints "Frontend sur http://localhost:5173" but `web/vite.config.ts` sets port **3000**.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ npm run dev
- [🚀 Installation Guide](./docs/INSTALLATION.md) - Complete installation instructions
- [⚙️ Configuration Guide](./docs/CONFIGURATION.md) - Environment variables and settings
- [🔧 Development Guide](./docs/DEVELOPMENT.md) - Set up development environment
- [🔐 Authentication](./docs/AUTHENTICATION.md) - Users, teams, permissions, API keys and single sign-on
- [🔐 Authentication](./docs/AUTHENTICATION.md) - Users, teams, permissions, API keys, single sign-on and the admin UI

### User Guides
- [📖 User Guide](./docs/USER_GUIDE.md) - How to use Tracker
Expand Down
32 changes: 29 additions & 3 deletions docs/AUTHENTICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,8 @@ every claim it needs must be in the `id_token`.
The local `admin` account keeps working next to SSO and is the way back in
when the IdP is misconfigured or down. SSO is off unless `AUTH_OIDC_ISSUER` is
set. When it is on, `GET /api/v1alpha1/auth/config` reports `oidcEnabled` and
`oidcButtonLabel`. The Single Sign-On button of the login page ships with the
web PR #201: until it is merged, start a login by opening
`/api/v1alpha1/auth/oidc/login` directly.
`oidcButtonLabel`, and the login page shows a Single Sign-On button that opens
`/api/v1alpha1/auth/oidc/login`.

### Configuration

Expand Down Expand Up @@ -471,6 +470,33 @@ instead of shutting it down.
Presenting no credential at all is unchanged: the caller is anonymous and gets
the anonymous permissions.

## Web UI

The web interface consumes the endpoints above:

| Page | Purpose | Requirement |
|------|---------|-------------|
| `/login` | Local sign-in form; SSO button when OIDC is configured | none |
| `/account/password` | Change the password of a local account; forced after first sign-in | signed-in user |
| `/admin/users` | List, create (username, email, temporary password, teams), edit teams, enable or disable accounts | `access:manage` |
| `/admin/teams` | List, create, edit permissions and OIDC groups, delete non built-in teams | `access:manage` |
| `/admin/api-keys` | List, create (team or global), reveal the secret once, revoke | `access:manage` |

Behaviour in the browser:

- A `401` on any API call redirects to `/login?redirect=<page>`; a `403` shows an
"Access denied" toast and leaves the page in place.
- Sidebar entries are hidden when the principal lacks the section's `*:read` permission, and
create, edit, delete and lock buttons are hidden without the matching `*:write` permission.
Hiding is a convenience: the backend enforces every permission.
- Anonymous visitors see a "Sign in" button; signed-in users get an account menu with their
source (`local` or `oidc`), teams, "Change password" (local accounts) and "Sign out".
- In `DEMO_MODE`, the banner tells anonymous visitors that browsing is read-only.
- When the UI is served by the Vite dev server (`npm run dev`, on port 3000) with the backend
running separately, start the backend with `AUTH_PUBLIC_URL=http://localhost:3000`, otherwise
the login endpoint's cross-site check refuses the request because the dev proxy rewrites the
Host header.

## Metrics

`tracker_auth_requests_total{principal,result}` counts authorization
Expand Down
11 changes: 11 additions & 0 deletions web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ VITE_SLACK_EVENTS_CHANNEL=
# API Configuration
VITE_API_BASE_URL=/api/v1alpha1

# Authentication
# Browser sessions use the HttpOnly "tracker_session" cookie set by the backend;
# no token is configured on the frontend. Automation uses the X-Api-Key header.
# In development the Vite proxy forwards /api to VITE_BACKEND_URL (default
# http://localhost:8080) so the cookie is first-party.
VITE_BACKEND_URL=http://localhost:8080
# When the UI runs through the Vite dev server (port 3000) and the backend runs
# separately, start the backend with AUTH_PUBLIC_URL=http://localhost:3000, or
# the login endpoint's cross-site check refuses the request because the dev
# proxy rewrites the Host header.

# Demo Mode Configuration
# Set to 'true' to show demo banner with Buy Me a Coffee link
VITE_DEMO_MODE=false
Expand Down
20 changes: 20 additions & 0 deletions web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,26 @@ Les fichiers de production seront générés dans le dossier `dist/`
npm run preview
```

## Authentication

The UI signs in against the backend described in [docs/AUTHENTICATION.md](../docs/AUTHENTICATION.md).

- `/login`: local account form, plus an SSO button when the backend reports `oidcEnabled`.
- Sessions are HttpOnly cookies; the axios client sends them with `withCredentials`. A `401`
redirects to `/login?redirect=<current page>`, a `403` shows an "Access denied" toast.
- Navigation entries and create/edit/delete buttons are hidden when the signed-in principal
(or the anonymous principal) lacks the matching `*:read` / `*:write` permission.
- `/admin/users`, `/admin/teams` and `/admin/api-keys` require `access:manage`.
- `/account/password` lets local users change their password; accounts flagged
`mustChangePassword` are sent there first.
- When this UI is served by the Vite dev server (`npm run dev`, port 3000) and the backend
runs separately, start the backend with `AUTH_PUBLIC_URL=http://localhost:3000`, otherwise
the login endpoint's cross-site check refuses the request.

Scripts: `npm test` (vitest), `npm run lint:auth` (ESLint on the auth modules),
`npm run typecheck` (`tsc --noEmit`, currently reports pre-existing errors outside the auth
modules; CI only fails on the auth modules).

## 🔧 Configuration

### Proxy API
Expand Down
Loading
Loading