Commit Graph
15 Commits
Author SHA1 Message Date
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
adebisi-fa 2ad4aa63c6 feat(fpagent): pin releases, report upgrades, restart the service
The installer gains --tag to install a specific release (gateway route
/fpagent/releases/tags/:tag), derives the git hash from the asset name,
and hands it to the bundle installer as VERSION so upgrades are reported
and the installed version is recorded. Service restart on upgrade lives
in the bundle installer, which owns the systemd unit.
2026-10-02 11:26:35 +01:00
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
adebisi-fa 1ddf61b59f feat(ui): icon-only header actions and side-nav separators
- Help and Sign in/out become icon-only Carbon cds-header-global-action
  items (real links, accessible name in a visually-hidden span); the
  header nav keeps just Home + the tools.
- Side nav gains a separator so Home, the tools, and Help/Sign In/Out
  form three groups.
- Drop the now-unused header-nav-divider rule.
2026-10-01 14:53:10 +01:00
adebisi-fa a218835a1c feat(ui): auth-gate the landing, add Help page and Home nav
- Landing shows the tool tiles only when signed in and a sign-in card
  otherwise; the login callback now returns to /.
- New public /help page: intro plus a per-tool Carbon accordion.
- Nav restructured to Home, {services}, divider, Help, Sign In/Out in
  both the header and the side nav; the header right keeps only the
  identity chip. Side-nav logo enlarged, border removed, spacing
  equalised. Sign-out modal no longer flashes before it is defined.
- Per-deploy asset versioning (?v=) with immutable asset caching and
  no-store HTML; help step lists show their numbers again.
- Tests updated and extended (48 total).
2026-10-01 14:39:50 +01:00
adebisi-fa 001290a789 feat(ui): icons for the mobile side-nav items
Crop / fingerprint-recognition / logout Carbon icons (16px, inlined into the
shared header so they inherit the theme colour) in the side-nav-link
'title-icon' slot — the slot is named title-icon, not nav-icon.
2026-10-01 06:21:27 +01:00
adebisi-fa 95200b6abf fix(cropper): make load failures visible; loud config errors
'Load next queued' did nothing because the running server had been started
without its env file (a restart used '-f .env' from tools/, where no .env
exists), so the cropper was unconfigured and every action answered 503 —
and none of that was visible, because notifications never rendered either:

- notifications used cds-toast/cds-toast-item, which the Carbon bundle does
  not define; a cds-toast-notification element renders nothing standalone in
  this build. The cropper now uses cds-inline-notification in the fixed
  toast viewport (renders, themed, close button) like the wizard
- an unconfigured deployment now says so on the page (503 'Cropper
  unavailable') instead of rendering actions that all fail, and logs a
  startup warning naming the missing credentials
- tests: notifications use a registered element; unconfigured page 503
2026-10-01 04:36:31 +01:00
adebisi-fa 2e4a51d018 feat(ui): Youreka favicon, logout confirmation, nav active states, brand alignment
- favicon is the Youreka logo (64px PNG generated from the source mark),
  on the tools pages and server error pages
- signing out asks first: a Carbon cds-modal (Cancel / danger Sign out)
  shared by the header and the mobile side nav; Cancel closes, Sign out
  confirms and logs out
- the current tool is now visibly selected: Carbon marks active nav via
  a[aria-current=page] INSIDE the shadow, but aria-current sits on the
  host, so the rule never matched — the header item now gets a blue
  underline and the side-nav link a blue left border (via ::part(link)
  for colour, plus the host for the marker)
- logo and wordmark are middle-aligned inside a flex brand wrapper
- tests: favicon/modal/signout-trigger markup and the active-tool marking
  on both navs (45 total)
2026-10-01 04:08:09 +01:00
adebisi-fa 17c407883d feat(ui): g100 dark theme and more space above page titles
- all pages (landing, both tools, server error pages) render on Carbon's
  g100 theme via <body class="app cds--g100">; token consumers follow
  automatically, including component shadow styles through the inherited
  --cds-* custom properties
- dark base on <html> so no light flash around the viewport
- step chips and tool icons use the brand blue: layer-selected-inverse
  inverts to a LIGHT token in g100, which had made them white-on-white
- page titles breathe: .page-head top margin 1.5rem -> 2.75rem
- new test asserting every page renders on g100 (44 total)
2026-10-01 04:01:44 +01:00
adebisi-fa 340d8da19d feat(ui): ui-shell header with tool links, mobile side nav, Youreka logo
- header: Carbon ui-shell (cds-header) with the Youreka logo (from
  idid-enterprise-portal), Image Cropper / Fingerprint Agent nav items
  (aria-current on the active tool), the agent status tag on the wizard,
  and the operator chip + Sign out on desktop
- mobile: cds-side-nav (logo above the items, both tools, a divider, then
  Sign out) toggled by the ui-shell menu button; account actions move out
  of the header below 1056px, the nav is hidden at desktop widths
- the header is built once in server.js (appHeader) and injected into the
  landing, both tools and error pages — no per-page duplication
- removed my duplicate menu-button toggle: ui-shell coordinates the menu
  button and side nav itself (the duplicate cancelled it out)
- fixes: 'cds-header' was hidden by a selector-list typo
  (cds-header, cds-side-nav:not(:defined)); the collapsed side nav no
  longer captures taps

Tools banner logo asset added (public/assets/youreka-logo.png).
2026-10-01 03:53:24 +01:00
adebisi-fa 22735c0709 refactor(ui): inject the session server-side instead of fetching it client-side
The pages are rendered by the server, which already holds the session — so
/fingerprint, /cropper and the landing now embed window.__SESSION__ (JSON,
with '<' escaped) instead of each fetching /auth/session on load:

- /fingerprint gets token, identity, agentUrl and the permitted domains
  (the same cached context the endpoint returned); /cropper gets identity
- landing nav is now server-rendered (Sign in vs user chip + Sign out);
  its client fetch script is deleted
- per-user HTML is sent with Cache-Control: no-store and Vary: Cookie
- express.static runs with index:false so '/' can be session-aware while
  assets stay session-free
- GET /auth/session remains as the documented JSON contract (and the
  canonical place the domain-filter caching is exercised in tests)

Client code shrinks by a fetch + error path per page; 3 new tests
(injection, escaping, landing header) — 43 total.
2026-10-01 03:28:31 +01:00
adebisi-fa 643e2c60be fix(security,reliability): address code-review findings
HIGH
- async handlers: Express 4 does not catch rejected promises and Node 22
  exits on unhandled rejections — a malformed body ({} to /cropper/dequeue
  etc.) could hang a request or kill the process. Every cropper route is
  now wrapped (throw → 500 via next), request bodies are validated up
  front (400), and JSON.parse of queue items is guarded. Server-wide
  error middleware added, honouring err.status (413 for oversized bodies).
- error pages HTML-escape all interpolated values — the login failure page
  rendered upstream (portal) error text raw.

MEDIUM
- outbound calls carry AbortSignal timeouts (5s OpenFGA verify, 15s
  cropper upstreams) — a hung upstream can no longer pin a request.
- body limits scoped: /cropper gets 50mb (picture data URLs), everything
  else 1mb (previously 50mb globally, memory-DoS surface pre-auth).
- cropper save validates the client-supplied domain against the catalog;
  picture fetches validate the Minio object key (id-domains/ prefix, no ..).

LOW
- static assets served before the session middleware (no PocketBase load
  per asset); landing header builds DOM nodes with textContent instead of
  innerHTML; picture load checks upstream ok.

5 new tests (40 total) covering malformed bodies, malformed queue items,
unknown domains, object-key traversal, and error-page escaping.
2026-10-01 03:12:02 +01:00
adebisi-fa 22d9ba40e3 fix(cropper): professional permission-denied page
Centered card with navbar, lock icon, sans-serif stack (error pages
previously rendered in the browser default serif), and action-aware
copy: permission denial offers only 'Back to Tools' instead of a
pointless 'Try again'; auth failures keep Try again + Back to Tools.
2026-10-01 01:35:37 +01:00
adebisi-fa d689434047 feat(cropper): move the Image Cropper into the tools app at /cropper
- Smart Photo Editor ported from id-image-bot: queue/by-ID picture
  loading, vue-croppa editing, carvekit background cleanup, nodered
  review flow, a2loft save — restyled to the tools design language
- unified login: YOUreka session identity, phone prefilled, OTP only at
  save (a2loft's requirement); artist-login step removed
- crop_image @ domain:idid OpenFGA gate on every cropper route: cached
  5-min on reads, live on save, fail closed
- credentials (minio/azure/nodered/carvekit) via env, sole exception to
  hardcoded config; 503 when unconfigured; native FormData replaces
  form-data; new deps minio + azure-storage
- 10 new tests (35 total) with fake minio/queue/nodered/removebg/a2loft
2026-10-01 01:13:30 +01:00
adebisi-fa f03a75b9d2 feat: YOUreka Tools express app — landing page + fpagent enrollment wizard
- tools/: express host (hardcoded config, YOUreka Auth cookie session,
  OpenFGA domain filtering with 5-min cache, a2loft proxies)
- /fingerprint wizard: domain picker, two-finger capture via fpagent
  Socket.IO, OTP-validated lookup/save, domain refresh control
- node --test suite (27 tests, fake portal/PocketBase/a2loft upstreams)
- Dockerfile (node:22-alpine, non-root, healthcheck)
- README rewritten; node_modules gitignored

Field-verified end to end: portal login, permitted-domain filtering,
real U.are.U 4500 captures, cancel-preserves-capture, a2loft save.
2026-09-30 23:12:51 +01:00