2026-09-30 23:12:51 +01:00
|
|
|
# tools-app Specification
|
|
|
|
|
|
|
|
|
|
## Purpose
|
|
|
|
|
|
|
|
|
|
Host the YOUreka tools web front-end: a landing page linking the Image Cropper
|
|
|
|
|
and the Fingerprint Agent wizard, YOUreka Auth cookie-session login, and
|
|
|
|
|
same-origin proxies to the a2loft identity API so browser calls never carry
|
|
|
|
|
operator credentials directly to third parties.
|
|
|
|
|
|
|
|
|
|
## Requirements
|
|
|
|
|
|
|
|
|
|
### Requirement: Landing page links the YOUreka tools
|
|
|
|
|
The app SHALL serve a landing page at `/` containing links to the Image
|
2026-10-01 01:13:52 +01:00
|
|
|
Cropper (`/cropper`) and to the Fingerprint Agent page (`/fingerprint`).
|
2026-09-30 23:12:51 +01:00
|
|
|
|
|
|
|
|
#### Scenario: Landing page renders both tool links
|
|
|
|
|
- **WHEN** a user opens the app root URL
|
|
|
|
|
- **THEN** the page shows a link to the Image Cropper pointing at
|
2026-10-01 01:13:52 +01:00
|
|
|
`/cropper` and a link to `/fingerprint`
|
2026-09-30 23:12:51 +01:00
|
|
|
### Requirement: YOUreka Auth login establishes a cookie session
|
|
|
|
|
The app SHALL authenticate operators through the YOUreka portal web-auth
|
|
|
|
|
flow: create a web auth session at the portal with a local callback URL,
|
|
|
|
|
redirect the operator to the portal's login URL, and on callback redeem the
|
|
|
|
|
returned session id for a PocketBase session cookie. The app SHALL persist
|
|
|
|
|
the PocketBase cookie value under its own httpOnly `session` cookie (30-day
|
|
|
|
|
max age) together with the operator's profile (first name, last name,
|
|
|
|
|
phone).
|
|
|
|
|
|
|
|
|
|
#### Scenario: Successful login round-trip
|
|
|
|
|
- **WHEN** the operator completes the portal login and returns with a
|
|
|
|
|
redeemable session id
|
|
|
|
|
- **THEN** the app sets the `session` and profile cookies and redirects to
|
|
|
|
|
`/fingerprint`
|
|
|
|
|
|
|
|
|
|
#### Scenario: Expired or rejected login link
|
|
|
|
|
- **WHEN** the callback session id cannot be redeemed (expired, timed out,
|
|
|
|
|
or incomplete verification)
|
|
|
|
|
- **THEN** the app shows an authentication error and does not set a session
|
|
|
|
|
cookie
|
|
|
|
|
|
|
|
|
|
### Requirement: Session persistence and refresh
|
|
|
|
|
The app SHALL recover the operator's PocketBase session from the `session`
|
|
|
|
|
cookie on every request, refreshing the token when it is invalid or expires
|
|
|
|
|
within 5 minutes, and SHALL persist the refreshed cookie value. When the
|
|
|
|
|
session cannot be recovered, the app SHALL treat the operator as anonymous
|
|
|
|
|
and clear the stale cookie.
|
|
|
|
|
|
|
|
|
|
#### Scenario: Refresh near expiry
|
|
|
|
|
- **WHEN** a request arrives with a session cookie whose token expires
|
|
|
|
|
within 5 minutes and the portal accepts a refresh
|
|
|
|
|
- **THEN** the app serves the request with the refreshed token and updates
|
|
|
|
|
the `session` cookie
|
|
|
|
|
|
|
|
|
|
#### Scenario: Unrecoverable session
|
|
|
|
|
- **WHEN** the stored token is expired and refresh fails
|
|
|
|
|
- **THEN** the request is treated as anonymous and the stale cookie is
|
|
|
|
|
cleared
|
|
|
|
|
|
|
|
|
|
### Requirement: Protected pages require a session
|
|
|
|
|
The app SHALL redirect anonymous operators to the login flow when they
|
|
|
|
|
request the Fingerprint Agent page, and SHALL return an authentication
|
|
|
|
|
error for session-requiring API calls made without a session.
|
|
|
|
|
|
|
|
|
|
#### Scenario: Anonymous access to the wizard page
|
|
|
|
|
- **WHEN** an operator without a valid session requests `/fingerprint`
|
|
|
|
|
- **THEN** the app redirects them to the login flow
|
|
|
|
|
|
|
|
|
|
### Requirement: Session endpoint exposes capture context
|
|
|
|
|
The app SHALL expose a session endpoint that, for an authenticated
|
|
|
|
|
operator, returns the current PocketBase token, the operator's phone and
|
|
|
|
|
name, the fpagent URL, the list of permitted domains (see the
|
|
|
|
|
fingerprint-wizard capability for the checking and caching rules), and the
|
|
|
|
|
time that set was last checked.
|
|
|
|
|
|
|
|
|
|
#### Scenario: Authenticated session fetch
|
|
|
|
|
- **WHEN** an authenticated operator fetches the session endpoint
|
|
|
|
|
- **THEN** the response includes their token, phone, name, the fpagent URL,
|
|
|
|
|
the permitted domains, and the time the set was last checked
|
|
|
|
|
|
|
|
|
|
### Requirement: Forced domain re-check endpoint
|
|
|
|
|
The app SHALL provide a force-refresh endpoint that, for the authenticated
|
|
|
|
|
operator, re-checks every catalog domain against the portal verify endpoint
|
|
|
|
|
bypassing the cache, replaces the cached result, and returns the updated
|
|
|
|
|
permitted-domain list with the new last-checked time. It SHALL refuse the
|
|
|
|
|
request while the 5-minute cache is still fresh, without contacting the
|
|
|
|
|
portal.
|
|
|
|
|
|
|
|
|
|
#### Scenario: Force-refresh after the cache elapses
|
|
|
|
|
- **WHEN** the cache period has elapsed and the operator's client calls the
|
|
|
|
|
force-refresh endpoint
|
|
|
|
|
- **THEN** every catalog domain is re-checked, the cache is replaced, and
|
|
|
|
|
the response carries the updated list and last-checked time
|
|
|
|
|
|
|
|
|
|
#### Scenario: Force-refresh refused while the cache is fresh
|
|
|
|
|
- **WHEN** the force-refresh endpoint is called within 5 minutes of the
|
|
|
|
|
last check
|
|
|
|
|
- **THEN** the request is refused with an error and no portal calls are
|
|
|
|
|
made
|
|
|
|
|
|
|
|
|
|
### Requirement: a2loft proxies inject operator identity
|
|
|
|
|
The app SHALL proxy staff lookup and fingerprint-save calls to
|
|
|
|
|
`https://apis.a2loft.net` under same-origin API routes. The proxy SHALL
|
|
|
|
|
attach the operator's phone from the server-side session and forward the
|
|
|
|
|
OTP supplied by the page as query parameters (`phone`, `otp`), preserving
|
|
|
|
|
the a2loft request and response shapes. The proxies SHALL require a
|
|
|
|
|
session; the OTP SHALL NOT be accepted without one.
|
|
|
|
|
|
|
|
|
|
#### Scenario: Staff lookup via proxy
|
|
|
|
|
- **WHEN** the page requests staff details for a domain and reference with
|
|
|
|
|
an OTP
|
|
|
|
|
- **THEN** the app calls a2loft's identity endpoint with the operator's
|
|
|
|
|
phone from the session and the supplied OTP, and returns a2loft's
|
|
|
|
|
response unchanged
|
|
|
|
|
|
|
|
|
|
#### Scenario: Unauthenticated proxy call rejected
|
|
|
|
|
- **WHEN** a proxy route is called without a valid session
|
|
|
|
|
- **THEN** the app rejects the call without contacting a2loft
|
|
|
|
|
|
|
|
|
|
### Requirement: Configuration is hardcoded
|
|
|
|
|
The app SHALL hardcode all configuration in source: port `4173`, portal
|
|
|
|
|
`https://portal.yrka.app`, PocketBase `https://pb.itcass.net`, a2loft
|
2026-10-01 01:13:52 +01:00
|
|
|
`https://apis.a2loft.net`, and agent `http://127.0.0.1:9000`. There SHALL be
|
|
|
|
|
no runtime configuration via environment variables or config files, with one
|
|
|
|
|
exception: the Image Cropper's service credentials (Minio keys, Azure storage
|
|
|
|
|
connection string, nodered credentials, cleanup-service key) SHALL come from
|
|
|
|
|
environment variables and SHALL NOT be stored in source.
|
2026-09-30 23:12:51 +01:00
|
|
|
|
|
|
|
|
#### Scenario: No configuration required to run
|
|
|
|
|
- **WHEN** the app is started with no environment variables or config files
|
|
|
|
|
- **THEN** it serves on port 4173 with the hardcoded external endpoints
|
2026-10-01 01:13:52 +01:00
|
|
|
|
|
|
|
|
#### Scenario: Cropper credentials from the environment
|
|
|
|
|
- **WHEN** the cropper credentials are supplied as environment variables
|
|
|
|
|
- **THEN** the cropper routes use them without any change to the hardcoded
|
2026-10-01 02:03:07 +01:00
|
|
|
URL/port configuration
|
|
|
|
|
|
|
|
|
|
### Requirement: Carbon Design System UI
|
|
|
|
|
The tools pages SHALL be built on the Carbon Design System: Carbon web
|
|
|
|
|
components (version-pinned via CDN) for buttons, notifications, toasts, tags,
|
|
|
|
|
progress and tiles; Carbon-styled native form controls; IBM Plex Sans
|
|
|
|
|
typography and Carbon color tokens for layout. Errors, confirmations and
|
|
|
|
|
successes SHALL surface as Carbon notifications rather than ad-hoc alert
|
|
|
|
|
dialogs.
|
|
|
|
|
|
|
|
|
|
#### Scenario: Pages render with Carbon components
|
|
|
|
|
- **WHEN** any tools page is opened
|
|
|
|
|
- **THEN** its interactive controls are Carbon components (or Carbon-styled
|
|
|
|
|
native controls) and its notifications render as Carbon notifications
|