Files

105 lines
4.7 KiB
Markdown
Raw Permalink Normal View History

# image-cropper Specification
## Purpose
The Image Cropper at `/cropper`: operators review, crop, clean and save
identity photos submitted for YOUreka ID domain profiles, with every proxied
action authorized by the crop_image relation.
## Requirements
### Requirement: Crop permission gate
Access to the cropper SHALL require the `crop_image` relation on `domain:idid`
(subsystem `idid`) for the signed-in operator's phone, verified against the
portal OpenFGA endpoint and failing closed on denial or error. Read paths
(page load, picture loading, background cleanup, dequeue) MAY serve a cached
result for up to 5 minutes; saving SHALL always verify live.
#### Scenario: Permitted operator opens the cropper
- **WHEN** an operator with the crop_image relation requests `/cropper`
- **THEN** the editor page loads
#### Scenario: Non-permitted operator is kept out
- **WHEN** an operator without the crop_image relation requests the cropper
page
- **THEN** the page renders a not-permitted explanation with no editor UI
#### Scenario: Cached gate on reads
- **WHEN** a read action occurs within 5 minutes of the operator's last
permission check
- **THEN** the cached result is used without calling the portal
### Requirement: Picture loading is gated and namespaced
The cropper SHALL load review pictures only through its namespaced routes
(`/cropper/picture/next`, `/cropper/picture/load/:id`,
`/cropper/picture/:file_name`) behind the session and crop gates. A denied
route SHALL return 403 without contacting the queue, storage, or review
services.
#### Scenario: Next queued picture
- **WHEN** the operator requests the next picture
- **THEN** the route proxies the Azure queue and returns the queue message
#### Scenario: Denied picture load does not reach services
- **WHEN** a request without the crop_image relation loads a picture route
- **THEN** the route answers 403 and makes no upstream calls
### Requirement: Background cleanup is gated
The background-cleanup proxy (`/cropper/api/removebg`) SHALL require the
session and crop gate, forwarding the cropped image to the cleanup service
only for permitted operators.
#### Scenario: Denied cleanup does not reach the cleanup service
- **WHEN** a request without the crop_image relation calls the cleanup proxy
- **THEN** it answers 403 without contacting the cleanup service
#### Scenario: Cleanup proxies the cleaned image
- **WHEN** a permitted operator submits a cropped image for cleanup
- **THEN** the route forwards it to the cleanup service and returns the
cleaned image
### Requirement: Saving requires a live gate and an OTP
Saving a cropped picture (`/cropper/profile/picture`) SHALL verify the crop
permission live (bypassing the cache) and SHALL authenticate the operator with
the session's PocketBase token, sent to a2loft as a bearer credential; no OTP
SHALL be collected or forwarded. The picture's domain SHALL come from a2loft's
own record for the upload (`dims/picture/{uploadId}` → `domainCode`), falling
back to the queue item's label, and SHALL match a path-safe pattern before it
is used in the request. A denied save SHALL not reach a2loft.
#### Scenario: Successful save
- **WHEN** a permitted operator saves
- **THEN** the route verifies the live gate and forwards the picture to
a2loft with the session token as a bearer credential and the profile's
resolved domain
#### Scenario: Denied save does not reach a2loft
- **WHEN** the live crop check denies, or the request carries no message body
- **THEN** the route answers 403/400 and makes no a2loft call
#### Scenario: Unsafe domain values are refused
- **WHEN** neither a2loft's record nor the queue label yields a path-safe
domain
- **THEN** the save is refused with 400 and a2loft is not contacted
### Requirement: Unified login — no separate artist login
The cropper SHALL NOT implement its own login. The operator's identity comes
from the YOUreka session and is presented to a2loft as the session's PocketBase
token; the picture's own metadata carries the domain. The cropper collects no
other credential — there is no save-time OTP.
#### Scenario: Phone prefilled from the session
- **WHEN** a signed-in operator opens the cropper
- **THEN** their phone is already known to the page without any login form
### Requirement: Cropper credentials come from the environment
The cropper's service credentials (Minio keys, Azure storage connection
string, nodered credentials, cleanup-service key) SHALL be read from
environment variables. When they are absent, the cropper routes SHALL answer
503 without attempting upstream calls; the application itself SHALL still
boot.
#### Scenario: Unconfigured deployment degrades gracefully
- **WHEN** the app starts without the cropper credentials
- **THEN** the other tools work and cropper routes answer 503