Consumes the a2loft cropper-token-auth change: the save now sends the
session's PocketBase token as a bearer credential and no phone/otp. The
backend authorizes with OpenFGA crop_image (domain:<code> -> domain:idid),
so an operator no longer needs super-user rights in the profile's domain —
which is what made saves fail with a blanket 'Invalid OTP specified.'.
- the save's domain comes from a2loft's own record of the picture
(dims/picture/{uploadId} -> domainCode), falling back to the queue label,
guarded by a path-safety pattern instead of the catalog check
- the 'core' -> 'uc' alias is deleted: 'core' is a2loft's own pseudo-domain
for a profile picture (special-cased there), so rewriting it changed the
attribution and the ID-card notification
- queue loads are enriched with the profile's domainCode; the panel shows
the profile's domain and, when it differs, the picture target
- the cropper's OTP field, state and parameters are gone; the fingerprint
wizard is untouched (its own OTP flow on its own endpoints)
- tests updated: bearer + no query auth, resolved domain wins, unsafe values
refused, no OTP field served (46 pass)
Requires the a2loft change deployed first.
2.1 KiB
2.1 KiB
Design
Context
See proposal.md. The a2loft cropper-token-auth change adds token mode to the
profile-picture endpoints: Authorization: Bearer <pb token>, identity from
the token's phone, authorized with OpenFGA crop_image on domain:<code> then
domain:idid. This change consumes it.
Goals / Non-Goals
Goals: the cropper saves with the session token and no OTP; the save's domain is a2loft's own record of the picture; the panel is truthful about the profile's domain.
Non-Goals: the fingerprint wizard's OTP flow; the cropper's lookup (there is none — pictures come from the queue); removing OTP support in a2loft.
Decisions
- Token sent from the server, never exposed to the browser beyond the
page's existing injected session: the proxy attaches
Authorization: Bearer ${req.session.token}. - Domain from a2loft, not the client:
dims/picture/{uploadId}is the record of the picture; itsdomainCodeis the profile's identity domain and itsdomainis the picture target (core). Sent asdomainCode, displayed as both. Falls back to the queue item's label when a2loft has no record (custom ids), guarded by a path-safety pattern. - The
core → ucalias is deleted:coreis a2loft's own pseudo-domain (special-cased ingetUserByPhoneAsync/updatePicture), so rewriting it — as the old bot did for its own calls — changed attribution and the notification. Authorization no longer depends on the domain, so no alias is needed. - Queue loads are enriched with
domainCodefor display (one extra read-only a2loft call per picture).
Risks / Trade-offs
- [a2loft record unavailable for an upload] → falls back to the queue label under the path-safety check; the save still carries the token for authz.
- [Operators outside the domain's super-users] → irrelevant now: the OpenFGA
check with the
domain:ididfallback authorizes them.
Migration Plan
Deploy a2loft first (dual-mode, no client change), then this change; the cropper saves without an OTP. Rollback = revert the proxy and re-add the field.
Open Questions
None.