Commit Graph
42 Commits
Author SHA1 Message Date
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
adebisi-fa eec76917bf FIx: send only fingerprint blob. 2021-12-08 17:34:01 +01:00
adebisi-fa 71b5c01adc Bug fix: Save fingerprint.
Bug fix: Show error whey device isn't plugged.
2021-12-08 16:14:57 +01:00
adebisi-fa 62ed8744f2 * Ported from NodeJS to Python
* Migrated to libfprint-2
2021-12-08 14:35:55 +01:00