Files
adebisi-fa 58b8b9c862 docs(openspec): move-image-cropper change — specs synced, change archived
image-cropper capability added; tools-app landing + config requirements
updated (internal /cropper link, credentials-via-env carve-out).
2026-10-01 01:13:52 +01:00

80 lines
3.9 KiB
Markdown

# Design
## Context
`id-image-bot` is three things: a Telegram intake bot (handlers disabled in
code), a picture-review pipeline (Azure queue `id-images`, Minio `files`
bucket, nodered review/process endpoints), and the operator web tool ("Smart
Photo Editor": Vue 2 + vue-croppa, phone/OTP login against a2loft). Only the
web tool moves; the bot and its deployment stay. The a2loft save route
(`POST /:domainCode/profile/picture`) accepts only `?phone=&otp=` — no key
variant — so the operator OTP survives, but only at save time. See proposal.md
for motivation.
## Goals / Non-Goals
**Goals:**
- One login story: YOUreka session everywhere; the cropper adds only a
save-time OTP (a2loft's requirement).
- Uniform `crop_image` authorization over every proxied cropper endpoint,
cheap for the portal (read cache, live at save).
- Faithful behavior: same queue/review/nodered flows and review-reason list.
**Non-Goals:**
- Moving the Telegram bot; decommissioning `spxa.yrka.app`.
- Changing the review/reject workflow or the review-reason catalog.
- Replacing toastr/swal UX patterns wholesale.
## Decisions
1. **Namespaced router, `tools/cropper.js` with DI.** `createCropperRouter({
minio, queue, fetch, requireApi, cropGate, a2loftBase, ... })` — the same
injectable pattern as the app factory, so tests fake minio/queue/upstreams.
Mounted at `/cropper` in server.js; the UI uses relative request paths.
2. **Unified login.** The bot app's phone+OTP middleware validated every
request; here the YOUreka session replaces it, and the one a2loft call that
needs an OTP (picture save) takes an operator-entered OTP forwarded with the
session phone and the picture's domain. Phone prefill from `/auth/session`.
3. **`crop_image` gate with read cache.** `permit.js` grows `cropPermission(
phone, {force})` beside the domain checker: same verifyOnce plumbing,
`relation: 'crop_image'`, object `domain:idid`, no fallback; results cached
per phone for 5 minutes, save forces live. Router-level middleware covers
every cropper route uniformly.
4. **Credentials via env, URLs hardcoded.** `MINIO_ACCESS_KEY`,
`MINIO_SECRET_KEY`, `AZURE_STORAGE_CONNECTION_STRING`,
`NODERED_HTTP_USERNAME`, `NODERED_HTTP_PASSWORD`, `CARVEKIT_API_KEY` — same
names the bot app used, so an existing `.env` is drop-in. Clients are built
only when the env is present; without it the router answers 503 and the
rest of the app is unaffected.
5. **Restyle to the tools design language.** Same stylesheet and panel
structure as `/fingerprint` (navbar, step chips, badges, buttons); toastr
stays for toasts. No behavior change beyond namespacing and auth.
6. **Native `FormData`** (Node 22) replaces the `form-data` dependency for the
cleanup proxy.
## Risks / Trade-offs
- [a2loft `validateOtp` may not recognize the YOUreka session phone format] →
the fingerprint walkthrough already saw a2loft accept the session phone on
lookups; the field pass confirms on the save path before cutover.
- [Cached crop gate lets a revoked operator act for ≤5 minutes] → reads only;
the save verifies live, and a2loft's OTP check remains the hard gate.
- [Review/process endpoints depend on nodered availability] → unchanged from
the bot app; failures surface as route errors, not crashes.
- [Two auth domains (mnet/ucth) disappear from the login] → the save validates
against the picture's domain; the field pass exercises a real item.
## Migration Plan
1. Land the cropper in `tools/` while `id-image-bot` keeps running unchanged.
2. Field pass: login → gate → load a real queued item (or by ID) → crop →
clean → save with OTP; confirm the picture lands in a2loft.
3. Repoint the landing card (done in the same change); leave `spxa.yrka.app`
running until the team confirms the new home.
4. Rollback: the old deployment still serves the tool.
## Open Questions
None — auth unification, secret handling and scope were confirmed with the
operator.