obscura-server 0.9.1

A server for relaying secure messages using the Signal Protocol
Documentation
# Profile Management

## 1. Overview
Users need a way to share a display name and avatar with other users without exposing this data to the server in plaintext.

## 2. Architecture: Persistent Attachments

### 2.1 The Problem with Current Attachments
Currently, the `AttachmentService` enforces a strict TTL (e.g., 30 days), after which files are garbage collected (deleted from DB and S3). Avatars need to persist indefinitely until changed.

### 2.2 Solution: Reference Strategy
Instead of a manual `is_persisted` flag (which is prone to sync errors), we will link the avatar directly to the user profile.

1.  **Database Schema**:
    -   Add `avatar_id` (UUID, Nullable) to the `users` table.
    -   FK: `users.avatar_id` references `attachments.id`.
2.  **Garbage Collector (Refined)**:
    -   The GC query becomes: "Delete attachments where `expires_at < NOW()` AND `id` is NOT present in `users.avatar_id`."
    -   This guarantees that as long as an avatar is "in use" by a profile, it is safe. As soon as the user changes it (updates `users.avatar_id`), the old one becomes eligible for GC (assuming it has expired).
    -   *Performance Requirement*: The `users.avatar_id` column **MUST be indexed**. The GC query utilizes a `NOT IN` or `NOT EXISTS` clause, which can trigger expensive table scans without an index on the referencing column.

### 2.3 Upload Flow (Atomic Swap)
1.  Client uploads new attachment (standard upload, gets standard TTL).
2.  Client calls `PUT /v1/profile` with `{ "avatarId": "new_uuid", ... }`.
3.  Server updates `users` table.
4.  Old avatar (no longer referenced) will eventually expire and be GC'd. No manual deletion logic required.

## 3. Profile Metadata & Key Distribution

### 3.1 Encryption
-   **Avatar**: Image is encrypted with a random AES key (`AvatarKey`) by the client before upload.
-   **Metadata**: Display Name + Avatar `attachment_id` + `AvatarKey` are bundled together.

### 3.2 Key Distribution (Capability-Based)
The server does not control who sees the profile. Access is granted via keys.
-   **Distribution**: User A sends their "Profile Key" to User B (e.g., during the initial handshake or inside the PreKey Bundle).
-   **Access**: User B uses the `attachment_id` to fetch the encrypted blob from S3, then uses the `AvatarKey` to view it.

### 3.3 Compatibility with Sealed Sender
Since the server only stores opaque blobs and `attachment_ids`, it does not need to know *who* is requesting an avatar, only that they are requesting a valid ID.
-   *Future enhancement*: We may need to sign avatar requests to prevent scraping, or rely on the fact that `attachment_id` is a random UUID (hard to guess).

## 4. Implementation Tasks
- [ ] Migration: Add `is_persisted` column to `attachments`.
- [ ] Backend: Update `AttachmentService` to handle persisted uploads.
- [ ] Backend: Update Garbage Collector logic.
- [ ] API: Add logic to manage the "Single Avatar" limit (delete old avatar when new one is uploaded).