Files
tools-legacy/openspec/changes/fingerprint-token-auth/design.md
T
adebisi-fa 14f6fd6a8e feat(fpagent): authenticate the wizard's a2loft calls with the session token
The fingerprint wizard's staff-lookup and fingerprint-save proxies forwarded the page's OTP with the session phone. a2loft resolves that phone against the domain being addressed, so an operator who holds only capture_biometric was rejected unless they were a domain super-user. The cropper already saves with the session token.

- tools/server.js: both a2loft proxies send Authorization: Bearer <session token>, no phone/otp; validateProxy drops the OTP and phone checks.
- tools/public/assets/fp: both OTP fields, otpLookup/otpSave state, request params and resets removed; canLookup/canSave no longer gate on OTP.
- Tests assert the Bearer header, no query auth, no OTP field in the served page, and both gates without a credential (66/66 pass).
- README and Help page updated.

Requires the a2loft fingerprint-token-auth change deployed first. OpenSpec change: fingerprint-token-auth.
2026-10-02 20:41:43 +01:00

2.0 KiB

Design

Context

See proposal.md for motivation. The wizard already holds the PocketBase token in the page (window.__SESSION__.token) for fpagent enrollment, and the cropper's save already sends Authorization: Bearer ${req.session.token} to a2loft through its own proxy (tools/cropper.js). The two remaining OTP calls are the wizard's staff-lookup and fingerprint-save proxies in tools/server.js; their handlers receive the a2loft URL from a2loftUrl/a2loftSaveUrl, which currently append phone+otp, and validateProxy rejects a missing otp.

Goals / Non-Goals

Goals: both wizard a2loft calls use the session token; no OTP in the wizard; proxy validation keeps rejecting unauthenticated calls; deployable after the a2loft change with a clean rollback.

Non-Goals: fpagent capture (already carries the token); the cropper; a2loft's own OTP compatibility; the legacy singular fingerprint endpoint.

Decisions

  1. Bearer from the server session, never the browser: the proxy attaches Authorization: Bearer ${req.session.token}, mirroring tools/cropper.js. The page's token copy stays for fpagent only.
  2. Drop the OTP check in validateProxy, along with the now-unnecessary phone check; keep the hardcoded-domain and reference checks. requireApi already guarantees a session.
  3. Remove the OTP inputs and state rather than hiding them; canLookup/canSave no longer require an OTP.

Risks / Trade-offs

  • [a2loft not yet dual-mode] → deploy the a2loft change first; this proxy change depends on it.
  • [Operators outside a domain's uploader list can now enroll] → intended; a2loft authorizes capture_biometric with the domain:idid fallback.
  • [Rollback] → revert the proxy and re-add the OTP fields; a2loft stays dual-mode.

Migration Plan

Deploy a2loft dual-mode → deploy this app → field pass: look up a reference and save both fingerprints with no OTP; a bogus-token call fails cleanly.

Open Questions

None.