Files
tools-legacy/README.md
T
adebisi-fa 14f6fd6a8e feat(fpagent): authenticate the wizard's a2loft calls with the session token
The fingerprint wizard's staff-lookup and fingerprint-save proxies forwarded the page's OTP with the session phone. a2loft resolves that phone against the domain being addressed, so an operator who holds only capture_biometric was rejected unless they were a domain super-user. The cropper already saves with the session token.

- tools/server.js: both a2loft proxies send Authorization: Bearer <session token>, no phone/otp; validateProxy drops the OTP and phone checks.
- tools/public/assets/fp: both OTP fields, otpLookup/otpSave state, request params and resets removed; canLookup/canSave no longer gate on OTP.
- Tests assert the Bearer header, no query auth, no OTP field in the served page, and both gates without a credential (66/66 pass).
- README and Help page updated.

Requires the a2loft fingerprint-token-auth change deployed first. OpenSpec change: fingerprint-token-auth.
2026-10-02 20:41:43 +01:00

10 KiB

YOUreka Tools

The web front-end for YOUreka ID field operations. A small Express app that hosts an authentication-aware landing page, an Image Cropper, a Fingerprint Agent enrollment wizard, and a public Help page.

Fingerprint captures run through the local fpagent binary (Socket.IO on 127.0.0.1:9000); this app handles login (YOUreka Auth), per-domain permissions, and the a2loft integration that reads and stores records. It is stateless — the operator's PocketBase session lives in an httpOnly cookie, so the container can run anywhere while fpagent stays on the field laptop.

Tools at a glance

Page Path Access Purpose
Landing / public Links the tools when signed in; shows a sign-in call to action when not
Fingerprint Agent /fingerprint signed in Enroll right-thumb and right-index fingerprints via the attached scanner
Image Cropper /cropper signed in + crop_image Review, crop, clean and save identity photos from the a2loft queue
Help /help public Intro plus a per-tool how-to (Carbon accordion)

Navigation

Every page shares one Carbon ui-shell header and mobile side nav, rendered server-side:

  • Home → /
  • Image Cropper, Fingerprint Agent → shown only when signed in
  • a divider, then Help
  • Sign in (anonymous) or Sign out (signed in; confirmed via a modal)

The landing page hides the two service tiles until you sign in, and the sign-in callback returns you to /.

Layout

tools/
├── server.js               # Express app: routes, session middleware, caching, proxies
├── yrka-auth.js            # YOUreka Auth cookie session → PocketBase (authRefresh)
├── permit.js               # OpenFGA permission checks + 5-minute cache
├── cropper.js              # /cropper router (queue, review, save) — injectable clients
├── public/
│   ├── index.html          # landing page shell (tiles injected per session)
│   ├── fpagent/
│   │   └── install.sh      # public /fpagent/install.sh (proxies private releases)
│   └── assets/
│       ├── style.css       # shared Carbon-styled layout + g100 theme
│       ├── help.html       # /help docs page
│       ├── fp/             # /fingerprint wizard (Vue 2 via CDN)
│       ├── cropper/        # /cropper page (Vue 2 + vue-croppa via CDN)
│       └── youreka-logo.png, youreka-favicon.png
└── test/                   # node --test suite (fake portal / PocketBase / a2loft), 48 tests

openspec/ at the repository root holds the design specs and the in-flight change proposals (see Design docs).

Running

Prerequisites: Node.js 22+. For the Fingerprint Agent you also need fpagent installed and running on this laptop (see the fingerprint-agent repository) with the U.are.U 4500 scanner attached.

cd tools
npm install
npm start            # → http://localhost:4173

Open http://localhost:4173 and sign in with your YOUreka account. Started this way (no cropper credentials) everything works except /cropper, which answers 503.

To run locally with the cropper, supply the credentials via dotenvx from the repository root:

dotenvx run -f .env -- node tools/server.js

fpagent must allow the tools origin. On a dev machine run it with: fpagent -allowed-origins http://localhost:4173

How it works

Authentication and sessions

Sign-in is the YOUreka portal web-auth flow: the app opens a portal auth session with a local callback URL, redirects the operator to the portal, and on callback redeems the session id for a PocketBase cookie. That value is stored under the app's own httpOnly session cookie (30-day max age) alongside the operator's profile. On every request the session is recovered server-side and refreshed when the token is invalid or expires within 5 minutes; an unrecoverable session is treated as anonymous and the stale cookie cleared.

Permissions

  • Fingerprint captures require capture_biometric per domain. The hardcoded catalog (UNICAL, Metropolitan Hotel, UCTH, UNICAL Radio International, YOUreka) is filtered against the portal's OpenFGA endpoint, cached for 5 minutes; the Refresh domains control unlocks once the cache expires. fpagent re-authorizes every capture regardless.
  • Image Cropper actions require crop_image. Reads may use the 5-minute cached verdict; the save always verifies live.

a2loft proxy

Staff lookup and the fingerprint save are proxied to https://apis.a2loft.net under same-origin routes. The proxy authenticates the operator with the signed-in session's PocketBase token, sent to a2loft as a Bearer credential; the operator never sends credentials to a third party directly. The cropper's save call uses the same token flow after a live permission check.

The Image Cropper

The /cropper page (Smart Photo Editor) lets operators work through the a2loft id-images queue: Load next queued or Load by upload ID, review the record, crop the photo, optionally Clean background, then accept or reject with reasons. Reads may use the cached crop_image verdict; saving re-verifies live and uploads through the queue.

Its service credentials live in dotenvx-encrypted files at the repository root — .env for development, .env.production for deployment. The fpagent installer key is read from the same files (see Installing the agent):

MINIO_ACCESS_KEY=…            MINIO_SECRET_KEY=…
AZURE_STORAGE_CONNECTION_STRING=…
NODERED_HTTP_USERNAME=…       NODERED_HTTP_PASSWORD=…
CARVEKIT_API_KEY=…

Without them the rest of the app works and /cropper answers 503. The private decryption keys sit in .env.keys (gitignored, never commit it).

dotenvx run -f .env -- node tools/server.js          # local, from the repo root
dotenvx set MINIO_SECRET_KEY "<value>" -f .env       # add/rotate a value

The Fingerprint Agent

The /fingerprint wizard walks the operator through:

  1. Locate profile — pick a permitted domain, enter the staff reference, and look up the record through the a2loft proxy.
  2. Capture — right thumb and right index finger, one capture at a time, with per-scan progress and cancel. The PocketBase token, domain and reference travel with every fprint-enroll to fpagent.
  3. Save — both templates are posted to a2loft authenticated with the session token.

The wizard tracks fpagent's scanner availability: the header tag and the capture panel show when the agent is offline or no reader is attached, and capture is disabled until a supported reader is detected. Availability means the reader is attached, not that a capture will succeed; enrolling still fails if the scanner is claimed or a scan does not complete.

Installing the agent

/fpagent/install.sh is a public installer (also served for curl | bash). It fetches the newest bundle from the private fingerprint-agent forge, verifies its sha256, then runs the bundle's own installer, which handles libfprint, the udev rule, plugdev and the systemd service. On a field laptop:

curl -fsSL https://tools.yrka.app/fpagent/install.sh | bash -s -- --enable-service
curl -fsSL https://tools.yrka.app/fpagent/install.sh | bash -s -- --check   # report only
curl -fsSL https://tools.yrka.app/fpagent/install.sh | bash -s -- --tag v1.0.0 --enable-service

Rerunning it is the upgrade path: it installs the newest release (or --tag for a pinned one), the bundle installer reports the version change, and with --enable-service it restarts a running service so the new binary takes effect. The version is the git hash carried in the asset name, stamped next to the binary (/usr/local/bin/fpagent.version) and compared on the next run.

The forge is private, so the operator's laptop needs no credentials: the app reads the release with GITEA_RELEASE_INSTALL_KEY and proxies it through /fpagent/releases/*. Without that key the installer answers 503.

Configuration

Everything except the credentials is hardcoded in tools/server.js by design — port 4173, portal https://portal.yrka.app, PocketBase https://pb.itcass.net, a2loft https://apis.a2loft.net, agent http://127.0.0.1:9000, forge https://sources.yrka.app and the domain catalog. Change them there. The only environment variables are the cropper credentials and the fpagent installer key GITEA_RELEASE_INSTALL_KEY above.

Deployment

Cache busting. Each process start generates a deploy id (DEPLOY_VERSION) that is appended to every /assets/... URL in served pages (?v=…), so browsers fetch fresh CSS/JS/images after a deploy. Versioned assets are cached hard (max-age=1y, immutable); the .html templates and all page routes are served no-store.

Stateless container. The container only serves the web front-end; fpagent keeps running on the operator's laptop (the wizard reaches it at http://127.0.0.1:9000 from the browser, wherever the container lives). The image bakes in the encrypted .env.production and runs through dotenvx; only the private key is supplied at runtime (never baked in):

docker build -t yreka-tools .
docker run -d -p 4173:4173 --name yreka-tools \
  --env DOTENV_PRIVATE_KEY_PRODUCTION="$(grep -oP '^DOTENV_PRIVATE_KEY_PRODUCTION=\K.*' .env.keys)" \
  yreka-tools

dotenvx run --strict makes the container exit rather than start with undecryptable credentials. Note that --env values are visible to docker inspect; on a shared host prefer an env file with restrictive permissions (--env-file deploy.keys) or your platform's secret store.

Behind a TLS-terminating proxy the app honours X-Forwarded-Proto (trust proxy is enabled), so session cookies get the Secure flag automatically.

Tests

cd tools
npm test             # node --test — 48 tests, no network or hardware required

The suite covers the auth round-trip, session refresh, permission caching, the cropper router and token auth, and the a2loft proxies against fakes. A full field walkthrough (real portal, PocketBase, a2loft, and U.are.U 4500) — login → domain filtering → staff lookup → two captures with cancel path → save → reset — has been verified end to end via Playwright.

Design docs

openspec/ holds the source-of-truth specs under openspec/specs/ (tools-app, fingerprint-wizard, image-cropper) and change proposals under openspec/changes/. See openspec/config.yaml for the workflow.