diff --git a/openspec/changes/fingerprint-token-auth/.openspec.yaml b/openspec/changes/archive/2026-10-06-fingerprint-token-auth/.openspec.yaml similarity index 100% rename from openspec/changes/fingerprint-token-auth/.openspec.yaml rename to openspec/changes/archive/2026-10-06-fingerprint-token-auth/.openspec.yaml diff --git a/openspec/changes/fingerprint-token-auth/design.md b/openspec/changes/archive/2026-10-06-fingerprint-token-auth/design.md similarity index 100% rename from openspec/changes/fingerprint-token-auth/design.md rename to openspec/changes/archive/2026-10-06-fingerprint-token-auth/design.md diff --git a/openspec/changes/fingerprint-token-auth/proposal.md b/openspec/changes/archive/2026-10-06-fingerprint-token-auth/proposal.md similarity index 100% rename from openspec/changes/fingerprint-token-auth/proposal.md rename to openspec/changes/archive/2026-10-06-fingerprint-token-auth/proposal.md diff --git a/openspec/changes/fingerprint-token-auth/specs/fingerprint-wizard/spec.md b/openspec/changes/archive/2026-10-06-fingerprint-token-auth/specs/fingerprint-wizard/spec.md similarity index 100% rename from openspec/changes/fingerprint-token-auth/specs/fingerprint-wizard/spec.md rename to openspec/changes/archive/2026-10-06-fingerprint-token-auth/specs/fingerprint-wizard/spec.md diff --git a/openspec/changes/fingerprint-token-auth/specs/tools-app/spec.md b/openspec/changes/archive/2026-10-06-fingerprint-token-auth/specs/tools-app/spec.md similarity index 100% rename from openspec/changes/fingerprint-token-auth/specs/tools-app/spec.md rename to openspec/changes/archive/2026-10-06-fingerprint-token-auth/specs/tools-app/spec.md diff --git a/openspec/changes/fingerprint-token-auth/tasks.md b/openspec/changes/archive/2026-10-06-fingerprint-token-auth/tasks.md similarity index 84% rename from openspec/changes/fingerprint-token-auth/tasks.md rename to openspec/changes/archive/2026-10-06-fingerprint-token-auth/tasks.md index 2d093e6..cc3de4b 100644 --- a/openspec/changes/fingerprint-token-auth/tasks.md +++ b/openspec/changes/archive/2026-10-06-fingerprint-token-auth/tasks.md @@ -25,5 +25,6 @@ - [x] 3.1 Run the full tools test suite (`node --test`) and confirm it passes. (66/66 pass.) -- [ ] 3.2 Field pass against a real domain (a2loft deployed): look up a - reference and save both fingerprints with no OTP. +- [x] 3.2 Field pass against a real domain (a2loft deployed): look up a + reference and save both fingerprints with no OTP. Verified in production: + the lookup and both fingerprint saves succeeded with no OTP. diff --git a/openspec/specs/fingerprint-wizard/spec.md b/openspec/specs/fingerprint-wizard/spec.md index b4538d4..8c5e7ef 100644 --- a/openspec/specs/fingerprint-wizard/spec.md +++ b/openspec/specs/fingerprint-wizard/spec.md @@ -114,16 +114,16 @@ and SHALL display the agent version reported on connect. ### 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 +up staff by reference (token-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. +saving both fingerprints through the a2loft proxy authenticated with the +session token. 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 @@ -137,24 +137,6 @@ details, captured artifacts) when starting a new enrollment. - **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 - ### Requirement: Scanner availability is shown and gates capture The wizard SHALL consume fpagent's `fprint-device-status` `{available, device, captureAgentId}` event and SHALL distinguish the agent @@ -195,3 +177,23 @@ guarantee a capture will succeed. - **WHEN** the agent socket disconnects - **THEN** the wizard shows the agent offline and does not present the reader as 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 diff --git a/openspec/specs/tools-app/spec.md b/openspec/specs/tools-app/spec.md index 5dd389c..e4fbf44 100644 --- a/openspec/specs/tools-app/spec.md +++ b/openspec/specs/tools-app/spec.md @@ -17,6 +17,7 @@ Cropper (`/cropper`) and to the Fingerprint Agent page (`/fingerprint`). - **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, @@ -100,17 +101,21 @@ portal. ### 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. +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 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 +- **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