The reason it is a table
Sharing and revoking is the walkthrough, authorized downloads is the private half, and the reference has the row.
A presigned URL cannot be revoked. It can only expire.
Once minted it is valid until its signature lapses, and nothing the owner does can call it back. So share this file, and let me take it back is not buildable on presigning alone — not with a shorter expiry, not with a second signature, not with anything short of rotating the credential every file in the bucket was signed with.
A share is therefore a row against an object. The token is looked up on every fetch, and revoking is a write that takes effect on the next request.
The row
| Field | |
|---|---|
token | The primary key, and the whole credential. High-entropy, server-generated |
objectId | What it grants read access to |
expiresAt | When it stops working. Null never expires — only revocation ends it |
revokedAt | When the owner withdrew it. Null while live |
createdAt | When it was minted |
Revoking does not delete
revokedAt is a timestamp, not a DELETE.
A revoked share stays readable as history, and a fetch against it answers storage/share_revoked — 410 Gone — rather than the storage/not_found a deleted row would produce.
That difference is for the person holding the link. This existed and was withdrawn is a fact they can act on; 404 leaves them wondering whether they mistyped it, and sends them to ask you.
Three routes
| Route | Who |
|---|---|
POST /storage/:id/shares | The owner. Mints a token |
DELETE /storage/:id/shares/:token | The owner. Withdraws it |
GET /storage/share/:token | Anyone holding the token |
The last one is public by design — the token is the credential, and requiring a session as well would defeat the point of a link you can send somebody.
What the three refusals mean
| Code | Status | When |
|---|---|---|
storage/share_expired | The link had a date and it has passed | |
storage/share_revoked | 410 | The owner withdrew it |
storage/not_found | 404 | No such token — or an object you may not see |
That last row is the one worth pausing on. An object you are not entitled to read answers not-found rather than forbidden, because a 403 confirms the object exists. The share surface inherits the same posture.
Choosing an expiry
A share with an expiry has two ways to end and a share without one has a single way. Both are legitimate:
Set a date when the link has a natural life — an invoice somebody needs this quarter, a download in a receipt email. The expiry is the cleanup you never have to remember to do.
Leave it null when the link is meant to last and the owner will decide — a portfolio image, a file shared with a collaborator. Revocation is the whole exit, and it is available on the day you need it, which is the property presigning could not give you.
Neither is a substitute for the visibility flag. A public object is readable by anyone with its id; a share is a separate credential over a private one, and revoking a share does not make a public object private.