max / makenotwork
| 1 | //! Which bucket an object lives in, and the authority a delete needs. |
| 2 | |
| 3 | /// Default presigned URL expiration. |
| 4 | /// 1 hour balances usability (large uploads over slow connections) against |
| 5 | /// security (limiting the window for URL leakage). Overridable per-call. |
| 6 | pub const PRESIGN_EXPIRY_SECS: u64 = 3600; |
| 7 | |
| 8 | /// Cache-Control value for immutable content (builds, audio, covers). |
| 9 | /// One year with immutable directive, Cloudflare and browsers cache indefinitely. |
| 10 | pub const CACHE_CONTROL_IMMUTABLE: &str = "public, max-age=31536000, immutable"; |
| 11 | |
| 12 | /// Capability proof required by every `StorageBackend` delete method. |
| 13 | /// |
| 14 | /// Direct S3 deletion is sealed off from route handlers: the delete methods |
| 15 | /// take `&S3DeleteAuthority`, so an accidental `s3.delete_object(key)` from a |
| 16 | /// handler does not compile. Route code must instead enqueue through |
| 17 | /// `pending_s3_deletions` (e.g. `routes::storage::enqueue_s3_orphan`), whose |
| 18 | /// worker applies the `is_s3_key_live` guard before deleting, so a handler |
| 19 | /// cannot blind-delete a key a live row still references. |
| 20 | /// |
| 21 | /// Minting is `pub(crate)` and confined by convention to the durable-deletion |
| 22 | /// paths, the scheduler deletion worker + cleanup (`scheduler/cleanup.rs`) and |
| 23 | /// the malware-quarantine scan worker (`scanning/worker.rs`). The build-time |
| 24 | /// guard test `routes_never_delete_s3_directly` fails if any file under |
| 25 | /// `src/routes/` names a delete method or mints an authority, so the seal can't |
| 26 | /// silently erode. |
| 27 | ); |
| 28 | |
| 29 | |
| 30 | /// Mint a deletion authority. Restricted to the sanctioned durable-deletion |
| 31 | /// paths; see the type docs. Route handlers cannot reach a sanctioned path, |
| 32 | /// and the guard test enforces that they don't mint one anyway. |
| 33 | pub |
| 34 | S3DeleteAuthority |
| 35 | |
| 36 | |
| 37 | |
| 38 | /// Which configured S3 backend an object lives in. |
| 39 | /// |
| 40 | /// The delete *verb* is type-sealed by [`S3DeleteAuthority`]; this seals the |
| 41 | /// bucket *noun*. The `pending_s3_deletions` queue stores the bucket as text, and |
| 42 | /// the deletion worker dispatches between the main and SyncKit S3 clients on that |
| 43 | /// text. This enum is the single source of truth for the `"main"`/`"synckit"` |
| 44 | /// spellings so an orphan-enqueue can't silently mis-tag a SyncKit object as |
| 45 | /// `main` (where the worker would delete it against the wrong client and leak it |
| 46 | /// forever), `enqueue_s3_orphan` requires an `S3Bucket`, not a bare string. |
| 47 | |
| 48 | |
| 49 | Main, |
| 50 | Synckit, |
| 51 | /// Public, CDN-served bucket (`cdn.makenot.work`). Holds ONLY the |
| 52 | /// immutably-public image kinds after promote (covers, gallery, item/project |
| 53 | /// images, content insertions); a paid object can never enter it, so its |
| 54 | /// blanket public-read policy is safe by construction. Staging is never here |
| 55 | ///, unscanned bytes stay in `Main`; the content object lands here only via |
| 56 | /// the cross-bucket promote (see `scanning::promote_staging_to_content`). |
| 57 | Public, |
| 58 | |
| 59 | |
| 60 | |
| 61 | /// The stored/text spelling for the deletion queue. |
| 62 | |
| 63 | match self |
| 64 | Main => "main", |
| 65 | Synckit => "synckit", |
| 66 | Public => "public", |
| 67 | |
| 68 | |
| 69 | |
| 70 | /// Parse a bucket tag read back from the queue. Unknown/legacy values map to |
| 71 | /// `Main` (the historical default), so a garbled row is still reaped against |
| 72 | /// a backend rather than wedging the queue. |
| 73 | |
| 74 | match s |
| 75 | "synckit" => Synckit, |
| 76 | "public" => Public, |
| 77 | _ => Main, |
| 78 | |
| 79 | |
| 80 | |
| 81 | |
| 82 | /// Deletion enqueue pair for a content-image key whose promote state is unknown. |
| 83 | /// |
| 84 | /// A CDN-image key is a private **staging** key (`staging/...`, in `Main`) until |
| 85 | /// [`crate::scanning::promote_staging_to_content`] repoints it to the public |
| 86 | /// **content** key (`{user}/c/{sha}.ext`, in `Public`). A given object is in |
| 87 | /// EXACTLY one bucket, but a delete/replace path can run in either state, so it |
| 88 | /// can't know which. Enqueue the key under BOTH buckets: the reaper deletes from |
| 89 | /// the bucket the object is in and no-ops the other (content keys are unique to |
| 90 | /// one bucket), and `is_s3_key_live` still guards each bucket against a live |
| 91 | /// reference. Only for the four CDN-served image surfaces; gated media |
| 92 | /// (audio/video/version/media) is always `Main` and insertions always `Main`. |
| 93 | |
| 94 | |
| 95 | , |
| 96 | , |
| 97 | ] |
| 98 | |
| 99 |