technical:features:30_signed_files_and_profile_media
Table of Contents
30 — Signed files and profile media
Technical reference. For task-based help, see the corresponding user guide.
Documentation status: reviewed against implementation · Reviewed: 2026-07-21 · Primary users: family/coach in an authorized subject context; owner for guide publishing · Surfaces: mobile web, coach portal, storage and platform API
Purpose
Private media is referenced by bucket/object path and accessed through short-lived signed URLs. The platform authorizes the relationship between actor, subject, record and path before asking storage to issue a URL; knowing an object path is never enough.
Bucket and purpose map
| Bucket | Expected context and object prefix | Typical content |
|---|---|---|
profile-images | Own users/<actorUserId>/…; coach download of another profile additionally names the authorized family subject | User avatar/profile image. |
baby-images | families/<familyUserId>/babies/<babyId>/… with baby and subject context | Baby profile image. |
activity-images | families/<familyUserId>/activities/<activityId>/… with activity context | Activity/nappy attachment. |
organization-branding | organizations/<organizationId>/… | Practice logo. |
getting-started-guides | guides/… | Published audience PDF. |
sleep-plan-pdfs | families/<familyUserId>/sleep-plans/… | Attached sleep-plan PDF. |
sleep-plan-images | families/<familyUserId>/sleep-plans/… | Images embedded/attached to a sleep plan. |
Upload flow
- The client validates file type/size for the feature and prepares an image when applicable.
- It constructs the required bucket, safe prefix and record context (family, baby, activity or organization).
POST /files/signed-upload-urlvalidates the context and returns a signed URL, expiry and—when supported—an upload token/method.- The client uploads directly to storage using the returned authorization. It can use the token path or signed PUT fallback.
- Only after upload succeeds does the feature save the object path/asset ID onto the profile, baby, activity, organization or plan record.
- A replacement changes the stored reference. A removal clears the reference through the owning record API.
Image preparation
| Use | Client preparation |
|---|---|
| Coach/family profile | Maximum side 512 px, quality around 0.8. |
| Baby image | Maximum side 768 px, quality around 0.8. |
| Activity image | Maximum side 1,280 px, quality around 0.8. |
| Organization logo | Maximum side 1,024 px, quality around 0.8; animated GIF is not recompressed. |
| Sleep-plan image in portal | JPEG/PNG/WebP/GIF, maximum 10 MB; non-GIF resized up to 2,000 px at about 0.82 quality. |
| Sleep-plan PDF in portal | PDF only, maximum 20 MB. |
The common image helper decodes/orients the image, previews it, chooses WebP/JPEG when the prepared result is smaller, and retains the original when conversion would not help. Preparation, signing and storage-upload errors remain distinguishable.
Download flow
- A feature sends
POST /files/signed-download-urlwith the stored path and required subject/record identifiers. - The platform resolves the referenced database record and re-runs read permission before signing.
- Default expiry is 900 seconds; callers may request up to 3,600 seconds.
- The client uses the URL for image/PDF rendering. Baby/profile helpers may cache the URL only until shortly before expiry (for example, a 30-second safety margin), then request a fresh one.
Server authorization matrix
| Asset | Upload rule | Download rule |
|---|---|---|
| Profile | Actor's own user prefix only. | Actor's own; coach may read a family profile only with subject context and current log-read access. |
| Baby | Current subject plus baby; caller must have activity-write access. | Current subject plus baby; caller must have activity-read access. |
| Activity | Existing activity plus matching family prefix; caller must have write access. | Existing activity plus matching subject; caller must have read access. |
| Organization logo | Eligible organization branding editor. | Active organization member or currently linked/assigned authorized family context. |
| Getting-started PDF | Super-admin upload, PDF, guides/ prefix. | Object must back a published guide whose audience the actor may read. |
| Sleep-plan PDF/image | Authorized coach assignment for upload; enforced type/prefix. | Coach needs current family access; family can read only the asset attached to a published plan. |
Validation and user-visible states
| State | Behaviour |
|---|---|
| Unsupported/oversized file | Feature rejects before signing/upload and explains the allowed type/size. |
| Image preparation failure | Existing asset stays in place; user can choose another file. |
| Signing denied | No storage URL is issued; surface shows access/error state. |
| Storage upload failure | Record reference is not advanced to the failed object. |
| Save-after-upload failure | Uploaded object can be orphaned; retrying the record save or re-upload is required. |
| URL expired | Rendering/open may fail; reload/reopen requests a fresh URL. |
| Missing object | Broken media state is contained; core record remains available. |
| Clear reference | UI returns to initials/placeholder or no attachment. |
Platform API contract
| Method and route | Request highlights | Response |
|---|---|---|
POST /files/signed-upload-url | bucket, objectPath, contentType, optional subject/record context and expiry | signedUrl, optional uploadToken, method and expiresAt. |
POST /files/signed-download-url | bucket, objectPath, required context and expiry | Short-lived signedUrl and expiresAt. |
The shared schemas enumerate the seven supported buckets, require safe object paths, cap expiry at one hour and reject unknown request fields.
Security and privacy
- Supabase Auth identifies the actor; the platform API performs tenant/assignment/record authorization before storage signing.
- Signed URLs are bearer credentials until expiry. Never persist them as the asset ID, put them in app analytics, or share them as permanent links.
- Prefix validation prevents an authorized call for one record from signing a different actor/tenant path.
- Family reads of sleep-plan media are publication-bound; an unpublished/draft attachment is not exposed by its path.
- Clearing a database reference is not guaranteed to delete the underlying storage object. There is no general user-facing storage-delete endpoint in this feature.
A signed URL is an authorization snapshot, not ongoing permission. Keep expiries short, request it just in time, and never infer access from a URL cached before assignment/publication changed.
Acceptance checklist
- [ ] Every bucket rejects paths outside its actor/family/organization prefix.
- [ ] Baby/activity/sleep-plan requests bind both identifiers and current permissions.
- [ ] Upload updates the owning record only after successful storage transfer.
- [ ] Type, size and image-preparation failures are actionable.
- [ ] Download URLs expire, refresh safely and are absent from durable app records/logs.
- [ ] Published-guide and published-sleep-plan rules are rechecked at signing time.
- [ ] Cross-tenant, revoked-assignment and stale-record signing tests fail closed.
- [ ] Product copy does not promise storage deletion when only a reference was cleared.
Source evidence
- Mobile web:
mobile-web/src/utils/image-upload.ts, signed-file/profile/baby/activity consumers andmobile-web/src/components/SignedFilePanel.tsx - Coach portal:
admin-web/src/routes/AccountPanel.tsx,admin-web/src/routes/BrandingPanel.tsx,admin-web/src/api/familyProfileArtifacts.ts - Platform:
platform/packages/shared/src/files.ts,platform/apps/api/src/routes/file-routes.ts,platform/apps/api/src/services/prisma-file-url-service.ts,platform/apps/api/src/services/storage-service.ts,platform/apps/api/src/services/getting-started-guide-service.ts
