Commit Graph
53 Commits
Author SHA1 Message Date
adebisi-fa fa33c6e179 feat(fpagent): pick the installer bundle for the host architecture
The hosted installer only ever chose the x86_64 bundle, so a 32-bit field
laptop running the public one-liner failed with "no compatible bundle" even
though fingerprint-agent v1.5.0 publishes fpagent-<version>-linux-i386.tar.gz.

Resolve the host architecture from uname -m (x86_64 or i386, else fail before
any download), then prefer -ubuntu-<codename>-<arch>.tar.gz and fall back to
-linux-<arch>.tar.gz. The version regex now derives the git hash for every
supported architecture, so a 32-bit install records and reports it too.

The help text matches the bundle installer: the systemd service and the
self-update timer are on by default, with --no-service, --no-updates and
--update-base documented.
2026-10-06 19:19:07 +01:00
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 548e33390c feat(fpagent): show scanner availability and gate capture
Consume fpagent's fprint-device-status {available, device, captureAgentId}
in the /fingerprint wizard. Availability is tracked tri-state so an agent
that does not report it leaves capture working as before; the header tag
shows offline / no-scanner / ready, the capture panel warns, and capture is
disabled until a supported reader is attached.

Adds a vm-sandbox test for the wizard logic and the OpenSpec change
fpagent-device-availability, archived with the fingerprint-wizard spec.
2026-10-02 15:12:16 +01:00
adebisi-fa 35d895574f docs(openspec): archive fpagent-install-upgrade and sync specs
Documents release pinning (--tag), the pinned-tag metadata route,
installed-version tracking with upgrade reporting, and restart of a
managed running service on upgrade; syncs the four requirements into the
fpagent-installer main spec.
2026-10-02 11:41:50 +01:00
adebisi-fa ff62770bcc docs(fpagent): document --tag and the upgrade path 2026-10-02 11:26:35 +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 0698a9c9a9 fix(fpagent): sweep scratch left by a SIGKILLed installer run
The EXIT trap cannot run on SIGKILL, so a killed run could leave its
fpagent-install.* temp dir behind. Sweep those before any network work,
scoped to our prefix and this user and only dirs older than a day so a
concurrent install is never touched.
2026-10-02 10:49:07 +01:00
adebisi-fa e3392c7750 docs(openspec): archive fpagent-release-installer and sync specs 2026-10-02 09:53:35 +01:00
adebisi-fa 46027acec1 chore(env): provision GITEA_RELEASE_INSTALL_KEY 2026-10-02 09:53:35 +01:00
adebisi-fa 6d3909c334 docs(fpagent): document the one-line installer and its key 2026-10-02 09:53: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 4d006e361f docs: rewrite README for the current project status
Restructure around the four pages, the nav, auth/sessions, permissions,
the a2loft proxy, configuration, deployment (incl. asset cache-busting)
and tests; fix the duplicate Docker heading and the stale test count.
2026-10-01 14:39:57 +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 134edb6ebd feat(ui): add Carbon icons to all tool buttons
Cropper: next, search, reset, trash-can, magic-wand, checkmark, misuse,
crop, undo, send, subtract.
Fingerprint Agent: search, renew, fingerprint-recognition, close, save.
2026-10-01 14:39:42 +01:00
adebisi-fa 3a5edbfe75 docs(openspec): cropper-token-auth field pass verified
Loaded a real upload (panel: Domain ucth · picture target core), saved with
no OTP — the proxy POSTed with the session token and no query auth, a2loft
returned ok and the panel reset.
2026-10-01 13:02:04 +01:00
adebisi-fa 796094f61b feat(cropper): save with the session token — no OTP, domain from a2loft
Consumes the a2loft cropper-token-auth change: the save now sends the
session's PocketBase token as a bearer credential and no phone/otp. The
backend authorizes with OpenFGA crop_image (domain:<code> -> domain:idid),
so an operator no longer needs super-user rights in the profile's domain —
which is what made saves fail with a blanket 'Invalid OTP specified.'.

- the save's domain comes from a2loft's own record of the picture
  (dims/picture/{uploadId} -> domainCode), falling back to the queue label,
  guarded by a path-safety pattern instead of the catalog check
- the 'core' -> 'uc' alias is deleted: 'core' is a2loft's own pseudo-domain
  for a profile picture (special-cased there), so rewriting it changed the
  attribution and the ID-card notification
- queue loads are enriched with the profile's domainCode; the panel shows
  the profile's domain and, when it differs, the picture target
- the cropper's OTP field, state and parameters are gone; the fingerprint
  wizard is untouched (its own OTP flow on its own endpoints)
- tests updated: bearer + no query auth, resolved domain wins, unsafe values
  refused, no OTP field served (46 pass)

Requires the a2loft change deployed first.
2026-10-01 12:37:25 +01:00
adebisi-fa 88d71ffec0 docs(cropper): explain why the queue's core label maps to uc
Evidence: GET {a2loft}/identity/{code}/units resolves domain codes — uc,
ucth, mtr, uri, yrka are valid, core answers 'Invalid domain code
specified.' The label comes from the Telegram intake hashtag, not a2loft.
2026-10-01 11:50:06 +01:00
adebisi-fa 645d3d2bc9 fix(cropper): alias the queue's core domain to uc; show server error text
Saving failed with 400 'Unknown domain.' because queue items carry the
legacy domain 'core', which the old bot mapped to 'uc' for a2loft URLs —
my domain validation (added in the review pass) rejected it. The save now
applies the same alias before validating and forwarding.

Also: the client showed axios's generic 'Request failed with status code
400' because the catch destructured error.message; all cropper handlers now
prefer the server's response.data.message (errMessage helper), so the real
reason is visible.

Verified: save with a 'core' item forwards to /identity/uc/profile/picture;
a 400 shows 'Unknown domain.'; 46 tests.
2026-10-01 10:35:02 +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 1f8fdb47b6 refactor(cropper): drop the cropped preview — the cleaned image is the only preview
The extra cropped-result panel duplicated what the crop pad already shows and
the cleaned image supersedes it. Removes the panel, its state, the pointer-
release refresh handlers and the app.js plumbing. The crop result is still
uploaded (croppa stays the source when nothing is cleaned).
2026-10-01 04:39:08 +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 cb5b151988 fix(cropper): render reason checkboxes, gate the Others field, add cropped preview
Three defects from the Carbon rewrite, found while fixing the review panel:

- reason checkboxes were invisible: Carbon's .cds--checkbox input is
  visually clipped and needs the .cds--form-item/.cds--checkbox-wrapper +
  .cds--checkbox-label markup to draw the box
- 'Others (please specify)' now appears only when 'Others not specified.'
  is ticked, and clears when unticked (hidden text must not be submitted);
  the textarea gets Carbon's __wrapper so it has a proper height
- new 'Cropped preview' between the original and cleaned previews, so the
  operator sees what will be uploaded; refreshed on pointer release over the
  pad and after load/clean
- REGRESSION: Vue.use(Croppa) had been dropped — the crop canvas never
  rendered, and saving only worked because the cleaned image bypassed
  generateDataUrl. Restored.
- REGRESSION: the OTP field bound v-model="otpSave" while the data property
  is "otp", so Vue's render threw ReferenceError and froze the panel as
  soon as accept was clicked (the OTP could never be entered). Bound to otp;
  the croppa instance is now reached via $refs.croppa (the v-model object is
  not always the live instance)
- after a successful save the review panel stayed open: reset() recreated an
  empty message object, which the template treats as truthy. message is null
  when idle now.

45 tests pass; save round-trip and reject/Others flow verified in the browser.
2026-10-01 04:26:26 +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 431a452d52 build: run the container through dotenvx with the encrypted .env.production
- image installs @dotenvx/dotenvx (pinned) and starts via
  'dotenvx run -f .env.production --strict -- node server.js'
- only DOTENV_PRIVATE_KEY_PRODUCTION is supplied at runtime; without it the
  container exits(1) (MISSING_PRIVATE_KEY) instead of booting with
  undecryptable credentials
- .env.production copied with --chown=node:node (the source mode is 600)
- .dockerignore excludes .env/.env.local/.env.keys so local envs and the
  private keys never enter the build context
2026-10-01 03:21:05 +01:00
adebisi-fa d1dadb2210 docs: dotenvx workflow for env files
.env.example moves to the repository root with dotenvx setup/run notes; the
README documents 'dotenvx run -f .env -- node tools/server.js' and the
Docker caveat (--env-file cannot read encrypted values).
2026-10-01 03:18:09 +01:00
adebisi-fa f68a7554e3 chore: commit dotenvx-encrypted .env and .env.production; ignore .env.keys
Values are encrypted (DOTENV_PUBLIC_KEY* is the only plaintext); the
private keys stay in .env.keys, which is gitignored. Run the app with
'dotenvx run -f .env -- node tools/server.js' locally.
2026-10-01 03:16:52 +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 3044986b04 chore: ignore .env files (credentials are environment-only) 2026-10-01 03:04:52 +01:00
adebisi-fa 76009efb86 fix(ui): session-aware landing header, clickable tiles, shell padding and spacing
- .container redefined on Carbon tokens (was Bootstrap's class — pages had
  no edge padding after the Bootstrap removal)
- landing tiles use cds-clickable-tile and — critically — matching closing
  tags: the fingerprint tile was parsed as NESTED inside the cropper tile
  (mismatched </cds-tile>), painting it on top and swallowing clicks
- landing header reflects the active session (user chip + Sign out instead
  of Sign in)
- vertical rhythm: larger panel padding, row gaps and section margins
2026-10-01 02:52:40 +01:00
adebisi-fa 930af97f05 feat(ui): rebuild all tools pages on Carbon Design System web components
- @carbon/web-components v2.50.0 via the IBM CDN (version-pinned);
  @carbon/styles CSS + IBM Plex Sans for tokens/typography
- landing: Carbon header + cds-tile tool cards
- fingerprint wizard: Carbon-styled form controls, cds-progress-bar scan
  progress, cds-tag badges, cds-inline-notification replacing swal
- cropper: cds-toast replacing toastr, cds-checkbox review reasons,
  cds-tag process badge; vue-croppa pad kept in the Carbon layout
- Bootstrap 4, swal, toastr and jQuery removed from all pages
- behavior frozen: same flows, endpoints, validation and copy
2026-10-01 02:03:07 +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 2f0a1fab21 fix(cropper): responsive editor layout
Two-column editor at >=900px (crop pad + cleaned preview left, cleaning/
review controls right), single column below; panel headers wrap their
hints onto their own line on narrow screens.
2026-10-01 01:22:18 +01:00
adebisi-fa 58b8b9c862 docs(openspec): move-image-cropper change — specs synced, change archived
image-cropper capability added; tools-app landing + config requirements
updated (internal /cropper link, credentials-via-env carve-out).
2026-10-01 01:13:52 +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 531c419e32 chore!: retire the python fingerprint service
app.py, requirement.txt, public/ (capture + clockio UIs), .config.json
handling and the identify test image are removed — capture now runs via
the fpagent binary and the web UI lives in tools/ (see fpagent-tools-ui).
2026-09-30 23:12:51 +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
adebisi-fa 109a69961a docs(openspec): fpagent-tools-ui change — proposal, specs, design, tasks
Synced main specs (tools-app, fingerprint-wizard); change archived at
2026-09-30-fpagent-tools-ui. Includes openspec project config and the
agent skills installed with it.
2026-09-30 23:12:51 +01:00
adebisi-fa cdb402cd18 Device name added to remote config. 2022-09-07 13:18:42 +01:00
adebisi-fa ae4cfa334e * Fix: Pin vue to version 2 2022-02-09 10:43:43 +01:00
adebisi-fa 3c5fbbf46b * Upgrade service to integrate with new FPrint OS. 2022-01-30 17:46:52 +01:00
adebisi-fa 8bbfb59c6b * Display app version.
* Fix: FPrint enroll start/stop toggle.
2022-01-26 11:14:22 +01:00
adebisi-fa f9ef35103c * Add .gitignore files. 2022-01-18 09:19:07 +01:00
adebisi-fa e48beab2f5 * Implement ClockIN service for IDID Biometrics
* Make Biometrics service configurable to domain
* Use native threading with flask (remove 'eventlet')
* Other minor UI adjustment.
2022-01-18 09:17:26 +01:00
adebisi-fa 1ec205e3d5 * Fix: Multiple profile fprint capture. 2021-12-13 13:59:09 +01:00
adebisi-fa c7af56e96c - Store process id in temporary variable. 2021-12-10 16:33:43 +01:00
adebisi-fa cbeb1a61fc Fix: Send only blob to API server. 2021-12-08 17:36:11 +01:00