adebisi-fa b3b75ffac0 feat(fpagent): public installer and private-release proxy
Serve /fpagent/install.sh (also curl|bash) and proxy the private
fingerprint-agent releases through /fpagent/releases/* using
GITEA_RELEASE_INSTALL_KEY, so a field laptop needs no forge credentials.
The installer picks the offline bundle for the host codename when present,
verifies the published sha256, then hands off to the bundle's own installer.
2026-10-02 09:53:35 +01:00
2021-12-08 14:35:55 +01:00

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)
│   └── 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 attaches the operator's phone from the server-side session and forwards the OTP supplied by the page; the operator never sends credentials to a third party directly. The cropper's save call instead authenticates with the session token (Bearer) 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 are the only environment variables this app uses. They live in dotenvx-encrypted files at the repository root — .env for development, .env.production for deployment:

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 OTP, 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 with a fresh OTP.

Configuration

Everything except the cropper 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, and the domain catalog. Change them there; there are no other environment variables or config files.

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.

S
Description
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.
Readme
920 KiB
Languages
JavaScript 74.7%
HTML 14.7%
CSS 6.9%
Shell 3%
Dockerfile 0.7%