157 lines
7.1 KiB
Markdown
157 lines
7.1 KiB
Markdown
# fingerprint-wizard Specification
|
|||
|
|
|
||
|
|
## Purpose
|
||
|
|
|
||
|
|
The Fingerprint Agent enrollment wizard at `/fingerprint`: offers the operator
|
||
|
|
only the domains they may capture for, runs two-finger enrollment through the
|
||
|
|
local fpagent capture agent, and saves the resulting templates to a2loft with
|
||
|
|
per-call OTP validation.
|
||
|
|
|
||
|
|
## Requirements
|
||
|
|
|
||
|
|
### Requirement: Permitted domain selection
|
||
|
|
The wizard SHALL offer, from the hardcoded catalog — UNICAL (`uc`),
|
||
|
|
Metropolitan Hotel (`mtr`), UCTH (`ucth`), UNICAL Radio International (`uri`),
|
||
|
|
YOUreka (`yrka`) — only the domains the operator is permitted to capture for.
|
||
|
|
Permission SHALL be determined after login against the same public portal
|
||
|
|
OpenFGA verify endpoint fpagent uses (`https://portal.yrka.app/api/openfga/verify`),
|
||
|
|
requesting the `capture_biometric` relation for `domain:<code>` on subsystem
|
||
|
|
`idid` for the operator's phone (leading `+` stripped), with a fallback check
|
||
|
|
against `domain:idid`; the operator is permitted if either check allows. Each
|
||
|
|
domain's checks SHALL fail closed on error or denial. Catalog checks SHALL run
|
||
|
|
in parallel, and their results SHALL be cached per operator phone for 5
|
||
|
|
minutes: session fetches within that window SHALL serve the cached result
|
||
|
|
without contacting the portal, and all catalog domains SHALL be re-checked in
|
||
|
|
parallel once that period expires.
|
||
|
|
|
||
|
|
#### Scenario: Permitted domain is offered
|
||
|
|
- **WHEN** the portal verify endpoint allows the operator for a catalog
|
||
|
|
domain
|
||
|
|
- **THEN** that domain appears in the wizard's domain selection
|
||
|
|
|
||
|
|
#### Scenario: Denied or unavailable domain is hidden
|
||
|
|
- **WHEN** the portal verify endpoint denies a domain, or the check errors
|
||
|
|
- **THEN** that domain is not offered
|
||
|
|
|
||
|
|
#### Scenario: Fallback object allows capture
|
||
|
|
- **WHEN** the `domain:<code>` check denies but the `domain:idid` check
|
||
|
|
allows
|
||
|
|
- **THEN** the domain is offered
|
||
|
|
|
||
|
|
#### Scenario: Cached result within the TTL
|
||
|
|
- **WHEN** a session fetch occurs within 5 minutes of the last permission
|
||
|
|
check for the operator
|
||
|
|
- **THEN** the cached permitted-domain list is served without calling the
|
||
|
|
portal verify endpoint
|
||
|
|
|
||
|
|
#### Scenario: Re-check after the TTL
|
||
|
|
- **WHEN** a session fetch occurs after the 5-minute cache period has
|
||
|
|
elapsed
|
||
|
|
- **THEN** all catalog domains are re-checked and the list reflects current
|
||
|
|
permissions
|
||
|
|
|
||
|
|
#### Scenario: No permitted domains
|
||
|
|
- **WHEN** no catalog domain is permitted for the operator
|
||
|
|
- **THEN** the wizard explains they are not permitted to capture
|
||
|
|
fingerprints for any domain and offers no capture UI
|
||
|
|
|
||
|
|
### Requirement: Manual refresh of permitted domains
|
||
|
|
The wizard SHALL offer a refresh control for the permitted-domain list that
|
||
|
|
stays disabled until the 5-minute cache period since the last check has
|
||
|
|
elapsed. Using it SHALL call the app's force-refresh endpoint, update the
|
||
|
|
offered domains, and update the displayed last-checked time; a refresh
|
||
|
|
refused because the cache is still fresh SHALL leave the current list and
|
||
|
|
state unchanged.
|
||
|
|
|
||
|
|
#### Scenario: Refresh control locked while the cache is fresh
|
||
|
|
- **WHEN** fewer than 5 minutes have passed since the permitted-domain set
|
||
|
|
was last checked
|
||
|
|
- **THEN** the refresh control is disabled
|
||
|
|
|
||
|
|
#### Scenario: Refresh after the cache elapses
|
||
|
|
- **WHEN** the cache period has elapsed and the operator uses the refresh
|
||
|
|
control
|
||
|
|
- **THEN** the wizard calls the force-refresh endpoint and the offered
|
||
|
|
domains update to the re-checked result
|
||
|
|
|
||
|
|
#### Scenario: Refused refresh leaves state unchanged
|
||
|
|
- **WHEN** a refresh request is refused because the cache is still fresh
|
||
|
|
- **THEN** the wizard keeps the current domain list and selection
|
||
|
|
|
||
|
|
### Requirement: Enrollment captures via fpagent
|
||
|
|
The wizard SHALL capture fingerprints through fpagent at the URL provided by
|
||
|
|
the session endpoint, emitting `fprint-enroll` with the session's PocketBase
|
||
|
|
token, the selected domain code, the finger name, and the staff reference. It
|
||
|
|
SHALL display capture progress from `fprint-enroll-status` events, record the
|
||
|
|
returned fingerprint template, image, and enrollment id on
|
||
|
|
`fprint-enroll-done` with `ok: true`, and surface the failure message and
|
||
|
|
return to idle on `ok: false`. The wizard SHALL offer cancellation of an
|
||
|
|
in-flight capture (emitting `fprint-enroll-stop`), SHALL prevent starting a
|
||
|
|
capture while another is in flight (fpagent serves one enrollment at a time),
|
||
|
|
and SHALL display the agent version reported on connect.
|
||
|
|
|
||
|
|
#### Scenario: Successful capture
|
||
|
|
- **WHEN** the operator starts a capture for a finger and fpagent reports
|
||
|
|
`fprint-enroll-done` with `ok: true`
|
||
|
|
- **THEN** the wizard records the fingerprint, image, and enrollment id for
|
||
|
|
that finger and displays the enrollment id
|
||
|
|
|
||
|
|
#### Scenario: Failed capture
|
||
|
|
- **WHEN** fpagent reports `fprint-enroll-done` with `ok: false`
|
||
|
|
(authentication failed, authorization unavailable, not authorized, or
|
||
|
|
capture failed)
|
||
|
|
- **THEN** the wizard shows the failure message and returns to the idle
|
||
|
|
state without recording artifacts
|
||
|
|
|
||
|
|
#### Scenario: Cancel in-flight capture
|
||
|
|
- **WHEN** the operator cancels a capture in progress
|
||
|
|
- **THEN** the wizard emits the stop event and returns to the idle state
|
||
|
|
|
||
|
|
#### Scenario: No concurrent captures
|
||
|
|
- **WHEN** a capture is in flight
|
||
|
|
- **THEN** the capture controls for both fingers are disabled until it
|
||
|
|
completes or is cancelled
|
||
|
|
|
||
|
|
### Requirement: Two-finger enrollment flow
|
||
|
|
The wizard SHALL enroll a profile by: selecting a permitted domain, looking
|
||
|
|
up staff by reference (OTP-validated through the a2loft proxy), displaying
|
||
|
|
the staff details, capturing the right thumb and right index finger, and
|
||
|
|
saving both fingerprints through the a2loft proxy with an OTP. The wizard
|
||
|
|
SHALL offer per-finger recapture until save, SHALL require both fingers
|
||
|
|
before save, and SHALL reset domain-independent state (reference, staff
|
||
|
|
details, captured artifacts) when starting a new enrollment.
|
||
|
|
|
||
|
|
#### Scenario: Complete enrollment
|
||
|
|
- **WHEN** the operator looks up staff, captures both fingers, and saves
|
||
|
|
with an OTP
|
||
|
|
- **THEN** both fingerprints are saved through the a2loft proxy and the
|
||
|
|
wizard reports success
|
||
|
|
|
||
|
|
#### Scenario: Save requires both fingers
|
||
|
|
- **WHEN** the operator attempts to save with only one finger captured
|
||
|
|
- **THEN** the wizard rejects the save and explains both fingers are
|
||
|
|
required
|
||
|
|
|
||
|
|
#### Scenario: New enrollment resets state
|
||
|
|
- **WHEN** the operator starts a new enrollment after a save
|
||
|
|
- **THEN** the reference, staff details, and captured finger artifacts are
|
||
|
|
cleared while the permitted domain list persists
|
||
|
|
|
||
|
|
### Requirement: OTP validated on every a2loft call
|
||
|
|
The wizard SHALL collect an OTP for each a2loft call — staff lookup and
|
||
|
|
fingerprint save — and pass it to the same-origin proxy; a2loft validates it.
|
||
|
|
Submission SHALL be blocked while the OTP is missing.
|
||
|
|
|
||
|
|
#### Scenario: Lookup with OTP
|
||
|
|
- **WHEN** the operator submits a reference with an OTP
|
||
|
|
- **THEN** the wizard calls the staff-lookup proxy with that OTP and
|
||
|
|
displays the returned staff details
|
||
|
|
|
||
|
|
#### Scenario: Save requires a fresh OTP
|
||
|
|
- **WHEN** the operator saves the captured fingerprints
|
||
|
|
- **THEN** the wizard sends the OTP entered for the save call
|
||
|
|
|
||
|
|
#### Scenario: Missing OTP blocks submission
|
||
|
|
- **WHEN** the OTP field is empty for a lookup or save
|
||
|
|
- **THEN** the corresponding submit control is disabled
|