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

3.9 KiB

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.