96 lines
4.2 KiB
Markdown
96 lines
4.2 KiB
Markdown
# Spec Delta: image-cropper
|
|||
|
|
|
||
|
|
## 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.
|
||
|
|
|
||
|
|
## ADDED 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 require an OTP from the
|
||
|
|
operator, forwarding the session phone as the validating identity together
|
||
|
|
with the OTP and the picture's domain to a2loft. A denied or OTP-less save
|
||
|
|
SHALL not reach a2loft.
|
||
|
|
|
||
|
|
#### Scenario: Successful save
|
||
|
|
- **WHEN** a permitted operator saves with an OTP
|
||
|
|
- **THEN** the route verifies the live gate and forwards the picture to
|
||
|
|
a2loft with the session phone, OTP, and picture domain
|
||
|
|
|
||
|
|
#### Scenario: Denied save does not reach a2loft
|
||
|
|
- **WHEN** the live crop check denies, or the OTP is missing
|
||
|
|
- **THEN** the route answers 403/400 and makes no a2loft call
|
||
|
|
|
||
|
|
### Requirement: Unified login — no separate artist login
|
||
|
|
The cropper SHALL NOT implement its own login. The operator's identity comes
|
||
|
|
from the YOUreka session (phone prefilled), the picture's metadata carries the
|
||
|
|
domain, and the only credential the operator supplies is the 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
|