# 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 Cropper (`/cropper`) and to the Fingerprint Agent page (`/fingerprint`). #### 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 `/cropper` and a link to `/fingerprint` ### 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 authenticate the operator with the signed-in session's PocketBase token, sent as an `Authorization: Bearer` credential, preserving the a2loft request and response shapes. The proxies SHALL require a session; a request without one SHALL be rejected without contacting a2loft. The proxies SHALL NOT forward or require an OTP. #### Scenario: Staff lookup via proxy - **WHEN** the page requests staff details for a domain and reference - **THEN** the app calls a2loft's identity endpoint with the session token as a bearer credential and returns a2loft's response unchanged #### Scenario: Fingerprint save via proxy - **WHEN** the page saves fingerprints for a domain and reference - **THEN** the app posts them to a2loft's fingerprint endpoint with the session token as a bearer credential #### 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 `https://apis.a2loft.net`, agent `http://127.0.0.1:9000`, and the fingerprint agent release forge `https://sources.yrka.app`. There SHALL be no runtime configuration via environment variables or config files, with two exceptions: 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; and the Fingerprint Agent installer's release key (`GITEA_RELEASE_INSTALL_KEY`) SHALL come from the environment and SHALL NOT be stored in source. #### 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 #### 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 URL/port configuration #### Scenario: Installer key from the environment - **WHEN** the fpagent installer release key is supplied as an environment variable - **THEN** the installer and release-proxy routes use it without any change to the hardcoded 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