docs(openspec): archive fingerprint-token-auth after the production pass

The wizard's a2loft calls now authenticate with the session token and carry no
OTP. Field pass in production: the lookup and both fingerprint saves succeeded
with no OTP, so task 3.2 is done. Merged the fingerprint-wizard and tools-app
deltas into the main specs and archived the change.
This commit is contained in:
2026-10-06 19:19:29 +01:00
parent 8d42faf707
commit 8a13fb527e
8 changed files with 43 additions and 35 deletions
@@ -25,5 +25,6 @@
- [x] 3.1 Run the full tools test suite (`node --test`) and confirm it passes. - [x] 3.1 Run the full tools test suite (`node --test`) and confirm it passes.
(66/66 pass.) (66/66 pass.)
- [ ] 3.2 Field pass against a real domain (a2loft deployed): look up a - [x] 3.2 Field pass against a real domain (a2loft deployed): look up a
reference and save both fingerprints with no OTP. reference and save both fingerprints with no OTP. Verified in production:
the lookup and both fingerprint saves succeeded with no OTP.
+26 -24
View File
@@ -114,16 +114,16 @@ and SHALL display the agent version reported on connect.
### Requirement: Two-finger enrollment flow ### Requirement: Two-finger enrollment flow
The wizard SHALL enroll a profile by: selecting a permitted domain, looking The wizard SHALL enroll a profile by: selecting a permitted domain, looking
up staff by reference (OTP-validated through the a2loft proxy), displaying up staff by reference (token-validated through the a2loft proxy), displaying
the staff details, capturing the right thumb and right index finger, and the staff details, capturing the right thumb and right index finger, and
saving both fingerprints through the a2loft proxy with an OTP. The wizard saving both fingerprints through the a2loft proxy authenticated with the
SHALL offer per-finger recapture until save, SHALL require both fingers session token. The wizard SHALL offer per-finger recapture until save, SHALL
before save, and SHALL reset domain-independent state (reference, staff require both fingers before save, and SHALL reset domain-independent state
details, captured artifacts) when starting a new enrollment. (reference, staff details, captured artifacts) when starting a new
enrollment.
#### Scenario: Complete enrollment #### Scenario: Complete enrollment
- **WHEN** the operator looks up staff, captures both fingers, and saves - **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 - **THEN** both fingerprints are saved through the a2loft proxy and the
wizard reports success wizard reports success
@@ -137,24 +137,6 @@ details, captured artifacts) when starting a new enrollment.
- **THEN** the reference, staff details, and captured finger artifacts are - **THEN** the reference, staff details, and captured finger artifacts are
cleared while the permitted domain list persists 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
### Requirement: Scanner availability is shown and gates capture ### Requirement: Scanner availability is shown and gates capture
The wizard SHALL consume fpagent's `fprint-device-status` The wizard SHALL consume fpagent's `fprint-device-status`
`{available, device, captureAgentId}` event and SHALL distinguish the agent `{available, device, captureAgentId}` event and SHALL distinguish the agent
@@ -195,3 +177,23 @@ guarantee a capture will succeed.
- **WHEN** the agent socket disconnects - **WHEN** the agent socket disconnects
- **THEN** the wizard shows the agent offline and does not present the reader as - **THEN** the wizard shows the agent offline and does not present the reader as
available available
### Requirement: Token authenticated on every a2loft call
The wizard SHALL authenticate each a2loft call — staff lookup and fingerprint
save — with the signed-in operator's PocketBase session token, sent by the
same-origin proxy as an `Authorization: Bearer` credential. The wizard SHALL
NOT collect an OTP.
#### Scenario: Lookup uses the session token
- **WHEN** the operator submits a reference
- **THEN** the wizard calls the staff-lookup proxy and a2loft authenticates the
session token
#### Scenario: Save uses the session token
- **WHEN** the operator saves the captured fingerprints
- **THEN** the wizard calls the fingerprint-save proxy and a2loft authenticates
the session token
#### Scenario: No OTP field
- **WHEN** the wizard page is served
- **THEN** it offers no OTP input for lookup or save
+14 -9
View File
@@ -17,6 +17,7 @@ Cropper (`/cropper`) and to the Fingerprint Agent page (`/fingerprint`).
- **WHEN** a user opens the app root URL - **WHEN** a user opens the app root URL
- **THEN** the page shows a link to the Image Cropper pointing at - **THEN** the page shows a link to the Image Cropper pointing at
`/cropper` and a link to `/fingerprint` `/cropper` and a link to `/fingerprint`
### Requirement: YOUreka Auth login establishes a cookie session ### Requirement: YOUreka Auth login establishes a cookie session
The app SHALL authenticate operators through the YOUreka portal web-auth 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, flow: create a web auth session at the portal with a local callback URL,
@@ -100,17 +101,21 @@ portal.
### Requirement: a2loft proxies inject operator identity ### Requirement: a2loft proxies inject operator identity
The app SHALL proxy staff lookup and fingerprint-save calls to The app SHALL proxy staff lookup and fingerprint-save calls to
`https://apis.a2loft.net` under same-origin API routes. The proxy SHALL `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 authenticate the operator with the signed-in session's PocketBase token, sent
OTP supplied by the page as query parameters (`phone`, `otp`), preserving as an `Authorization: Bearer` credential, preserving the a2loft request and
the a2loft request and response shapes. The proxies SHALL require a response shapes. The proxies SHALL require a session; a request without one
session; the OTP SHALL NOT be accepted without one. SHALL be rejected without contacting a2loft. The proxies SHALL NOT forward or
require an OTP.
#### Scenario: Staff lookup via proxy #### Scenario: Staff lookup via proxy
- **WHEN** the page requests staff details for a domain and reference with - **WHEN** the page requests staff details for a domain and reference
an OTP - **THEN** the app calls a2loft's identity endpoint with the session token as a
- **THEN** the app calls a2loft's identity endpoint with the operator's bearer credential and returns a2loft's response unchanged
phone from the session and the supplied OTP, 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 #### Scenario: Unauthenticated proxy call rejected
- **WHEN** a proxy route is called without a valid session - **WHEN** a proxy route is called without a valid session