upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/96.md
diff options
context:
space:
mode:
authorVitor Pamplona <vitor@vitorpamplona.com>2024-12-19 10:15:46 -0500
committerGitHub <noreply@github.com>2024-12-19 10:15:46 -0500
commitf7d97f3f40dc6677e6f996ffff431142ffe05810 (patch)
tree2eaa8c8628cc9a4b05811ed3869e21221fd85307 /96.md
parentd5b77b6d7348061cd0f16adc35e4d02a3a0b7380 (diff)
parent561059ff85c171b87a12b8381b724b4afc569a97 (diff)
Merge branch 'master' into draft-event
Diffstat (limited to '96.md')
-rw-r--r--96.md157
1 files changed, 99 insertions, 58 deletions
diff --git a/96.md b/96.md
index f7d901f..3828e76 100644
--- a/96.md
+++ b/96.md
@@ -19,7 +19,7 @@ will not have to learn anything about nostr relays.
19 19
20File storage servers wishing to be accessible by nostr users should opt-in by making available an https route at `/.well-known/nostr/nip96.json` with `api_url`: 20File storage servers wishing to be accessible by nostr users should opt-in by making available an https route at `/.well-known/nostr/nip96.json` with `api_url`:
21 21
22```js 22```jsonc
23{ 23{
24 // Required 24 // Required
25 // File upload and deletion are served from this url 25 // File upload and deletion are served from this url
@@ -59,7 +59,7 @@ File storage servers wishing to be accessible by nostr users should opt-in by ma
59 "file_expiration": [14, 90], 59 "file_expiration": [14, 90],
60 "media_transformations": { 60 "media_transformations": {
61 "image": [ 61 "image": [
62 'resizing' 62 "resizing"
63 ] 63 ]
64 } 64 }
65 } 65 }
@@ -84,57 +84,57 @@ it must use the "api_url" field instead.
84 84
85See https://github.com/aljazceru/awesome-nostr#nip-96-file-storage-servers. 85See https://github.com/aljazceru/awesome-nostr#nip-96-file-storage-servers.
86 86
87## Auth
88
89When indicated, `clients` must add an [NIP-98](98.md) `Authorization` header (**optionally** with the encoded `payload` tag set to the base64-encoded 256-bit SHA-256 hash of the file - not the hash of the whole request body).
90
87## Upload 91## Upload
88 92
89A file can be uploaded one at a time to `https://your-file-server.example/custom-api-path` (route from `https://your-file-server.example/.well-known/nostr/nip96.json` "api_url" field) as `multipart/form-data` content type using `POST` method with the file object set to the `file` form data field. 93`POST $api_url` as `multipart/form-data`.
90 94
91`Clients` must add an [NIP-98](98.md) `Authorization` header (**optionally** with the encoded `payload` tag set to the base64-encoded 256-bit SHA-256 hash of the file - not the hash of the whole request body). 95**AUTH required**
92If using an html form, use an `Authorization` form data field instead.
93 96
94These following **optional** form data fields MAY be used by `servers` and SHOULD be sent by `clients`: 97List of form fields:
95- `expiration`: string of the UNIX timestamp in seconds. Empty string if file should be stored forever. The server isn't required to honor this;
96- `size`: string of the file byte size. This is just a value the server can use to reject early if the file size exceeds the server limits;
97- `alt`: (recommended) strict description text for visibility-impaired users;
98- `caption`: loose description;
99- `media_type`: "avatar" or "banner". Informs the server if the file will be used as an avatar or banner. If absent, the server will interpret it as a normal upload, without special treatment;
100- `content_type`: mime type such as "image/jpeg". This is just a value the server can use to reject early if the mime type isn't supported.
101 98
99- `file`: **REQUIRED** the file to upload
100- `caption`: **RECOMMENDED** loose description;
101- `expiration`: UNIX timestamp in seconds. Empty string if file should be stored forever. The server isn't required to honor this.
102- `size`: File byte size. This is just a value the server can use to reject early if the file size exceeds the server limits.
103- `alt`: **RECOMMENDED** strict description text for visibility-impaired users.
104- `media_type`: "avatar" or "banner". Informs the server if the file will be used as an avatar or banner. If absent, the server will interpret it as a normal upload, without special treatment.
105- `content_type`: mime type such as "image/jpeg". This is just a value the server can use to reject early if the mime type isn't supported.
106- `no_transform`: "true" asks server not to transform the file and serve the uploaded file as is, may be rejected.
102 107
103Others custom form data fields may be used depending on specific `server` support. 108Others custom form data fields may be used depending on specific `server` support.
104The `server` isn't required to store any metadata sent by `clients`. 109The `server` isn't required to store any metadata sent by `clients`.
105 110
106Note for `clients`: if using an HTML form, it is important for the `file` form field to be the **last** one, or be re-ordered right before sending or be appended as the last field of XHR2's FormData object.
107
108The `filename` embedded in the file may not be honored by the `server`, which could internally store just the SHA-256 hash value as the file name, ignoring extra metadata. 111The `filename` embedded in the file may not be honored by the `server`, which could internally store just the SHA-256 hash value as the file name, ignoring extra metadata.
109The hash is enough to uniquely identify a file, that's why it will be used on the "download" and "delete" routes. 112The hash is enough to uniquely identify a file, that's why it will be used on the `download` and `delete` routes.
110
111The `server` MUST link the user's `pubkey` string (which is embedded in the decoded header value) as the owner of the file so to later allow them to delete the file.
112Note that if a file with the same hash of a previously received file (so the same file) is uploaded by another user, the server doesn't need to store the new file.
113It should just add the new user's `pubkey` to the list of the owners of the already stored file with said hash (if it wants to save space by keeping just one copy of the same file, because multiple uploads of the same file results in the same file hash).
114
115The `server` MAY also store the `Authorization` header/field value (decoded or not) for accountability purpose as this proves that the user with the unique pubkey did ask for the upload of the file with a specific hash. However, storing the pubkey is sufficient to establish ownership.
116 113
117The `server` MUST reject with 413 Payload Too Large if file size exceeds limits. 114The `server` MUST link the user's `pubkey` string as the owner of the file so to later allow them to delete the file.
118 115
119The `server` MUST reject with 400 Bad Request status if some fields are invalid. 116`no_transform` can be used to replicate a file to multiple servers for redundancy, clients can use the [server list](#selecting-a-server) to find alternative servers which might contain the same file. When uploading a file and requesting `no_transform` clients should check that the hash matches in the response in order to detect if the file was modified.
120 117
121The `server` MUST reply to the upload with 200 OK status if the `payload` tag value contains an already used SHA-256 hash (if file is already owned by the same pubkey) or reject the upload with 403 Forbidden status if it isn't the same of the received file. 118### Response codes
122 119
123The `server` MAY reject the upload with 402 Payment Required status if the user has a pending payment (Payment flow is not strictly required. Server owners decide if the storage is free or not. Monetization schemes may be added later to correlated NIPs.). 120- `200 OK`: File upload exists, but is successful (Existing hash)
124 121- `201 Created`: File upload successful (New hash)
125On successful uploads the `server` MUST reply with **201 Created** HTTP status code or **202 Accepted** if a `processing_url` field is added 122- `202 Accepted`: File upload is awaiting processing, see [Delayed Processing](#delayed-processing) section
126to the response so that the `client` can follow the processing status (see [Delayed Processing](#delayed-processing) section). 123- `413 Payload Too Large`: File size exceeds limit
124- `400 Bad Request`: Form data is invalid or not supported.
125- `403 Forbidden`: User is not allowed to upload or the uploaded file hash didnt match the hash included in the `Authorization` header `payload` tag.
126- `402 Payment Required`: Payment is required by the server, **this flow is undefined**.
127 127
128The upload response is a json object as follows: 128The upload response is a json object as follows:
129 129
130```js 130```jsonc
131{ 131{
132 // "success" if successful or "error" if not 132 // "success" if successful or "error" if not
133 status: "success", 133 "status": "success",
134 // Free text success, failure or info message 134 // Free text success, failure or info message
135 message: "Upload successful.", 135 "message": "Upload successful.",
136 // Optional. See "Delayed Processing" section 136 // Optional. See "Delayed Processing" section
137 processing_url: "...", 137 "processing_url": "...",
138 // This uses the NIP-94 event format but DO NOT need 138 // This uses the NIP-94 event format but DO NOT need
139 // to fill some fields like "id", "pubkey", "created_at" and "sig" 139 // to fill some fields like "id", "pubkey", "created_at" and "sig"
140 // 140 //
@@ -143,9 +143,9 @@ The upload response is a json object as follows:
143 // and, optionally, all file metadata the server wants to make available 143 // and, optionally, all file metadata the server wants to make available
144 // 144 //
145 // nip94_event field is absent if unsuccessful upload 145 // nip94_event field is absent if unsuccessful upload
146 nip94_event: { 146 "nip94_event": {
147 // Required tags: "url" and "ox" 147 // Required tags: "url" and "ox"
148 tags: [ 148 "tags": [
149 // Can be same from /.well-known/nostr/nip96.json's "download_url" field 149 // Can be same from /.well-known/nostr/nip96.json's "download_url" field
150 // (or "api_url" field if "download_url" is absent or empty) with appended 150 // (or "api_url" field if "download_url" is absent or empty) with appended
151 // original file hash. 151 // original file hash.
@@ -166,12 +166,12 @@ The upload response is a json object as follows:
166 // The server can but does not need to store this value. 166 // The server can but does not need to store this value.
167 ["x", "543244319525d9d08dd69cb716a18158a249b7b3b3ec4bbde5435543acb34443"], 167 ["x", "543244319525d9d08dd69cb716a18158a249b7b3b3ec4bbde5435543acb34443"],
168 // Optional. Recommended for helping clients to easily know file type before downloading it. 168 // Optional. Recommended for helping clients to easily know file type before downloading it.
169 ["m", "image/png"] 169 ["m", "image/png"],
170 // Optional. Recommended for helping clients to reserve an adequate UI space to show the file before downloading it. 170 // Optional. Recommended for helping clients to reserve an adequate UI space to show the file before downloading it.
171 ["dim", "800x600"] 171 ["dim", "800x600"]
172 // ... other optional NIP-94 tags 172 // ... other optional NIP-94 tags
173 ], 173 ],
174 content: "" 174 "content": ""
175 }, 175 },
176 // ... other custom fields (please consider adding them to this NIP or to NIP-94 tags) 176 // ... other custom fields (please consider adding them to this NIP or to NIP-94 tags)
177} 177}
@@ -179,11 +179,13 @@ The upload response is a json object as follows:
179 179
180Note that if the server didn't apply any transformation to the received file, both `nip94_event.tags.*.ox` and `nip94_event.tags.*.x` fields will have the same value. The server MUST link the saved file to the SHA-256 hash of the **original** file before any server transformations (the `nip94_event.tags.*.ox` tag value). The **original** file's SHA-256 hash will be used to identify the saved file when downloading or deleting it. 180Note that if the server didn't apply any transformation to the received file, both `nip94_event.tags.*.ox` and `nip94_event.tags.*.x` fields will have the same value. The server MUST link the saved file to the SHA-256 hash of the **original** file before any server transformations (the `nip94_event.tags.*.ox` tag value). The **original** file's SHA-256 hash will be used to identify the saved file when downloading or deleting it.
181 181
182`Clients` may upload the same file to one or many `servers`. 182`clients` may upload the same file to one or many `servers`.
183After successful upload, the `client` may optionally generate and send to any set of nostr `relays` a [NIP-94](94.md) event by including the missing fields. 183After successful upload, the `client` may optionally generate and send to any set of nostr `relays` a [NIP-94](94.md) event by including the missing fields.
184 184
185Alternatively, instead of using NIP-94, the `client` can share or embed on a nostr note just the above url. 185Alternatively, instead of using NIP-94, the `client` can share or embed on a nostr note just the above url.
186 186
187`clients` may also use the tags from the `nip94_event` to construct an `imeta` tag
188
187### Delayed Processing 189### Delayed Processing
188 190
189Sometimes the server may want to place the uploaded file in a processing queue for deferred file processing. 191Sometimes the server may want to place the uploaded file in a processing queue for deferred file processing.
@@ -200,12 +202,12 @@ the file processing is done.
200 202
201If the processing isn't done, the server should reply at the `processing_url` url with **200 OK** and the following JSON: 203If the processing isn't done, the server should reply at the `processing_url` url with **200 OK** and the following JSON:
202 204
203``` 205```jsonc
204{ 206{
205 // It should be "processing". If "error" it would mean the processing failed. 207 // It should be "processing". If "error" it would mean the processing failed.
206 status: "processing", 208 "status": "processing",
207 message: "Processing. Please check again later for updated status.", 209 "message": "Processing. Please check again later for updated status.",
208 percentage: 15 // Processing percentage. An integer between 0 and 100. 210 "percentage": 15 // Processing percentage. An integer between 0 and 100.
209} 211}
210``` 212```
211 213
@@ -219,7 +221,7 @@ However, for all file actions, such as download and deletion, the **original** f
219 221
220## Download 222## Download
221 223
222`Servers` must make available the route `https://your-file-server.example/custom-api-path/<sha256-file-hash>(.ext)` (route taken from `https://your-file-server.example/.well-known/nostr/nip96.json` "api_url" or "download_url" field) with `GET` method for file download. 224`GET $api_url/<sha256-hash>(.ext)`
223 225
224The primary file download url informed at the upload's response field `nip94_event.tags.*.url` 226The primary file download url informed at the upload's response field `nip94_event.tags.*.url`
225can be that or not (it can be any non-standard url the server wants). 227can be that or not (it can be any non-standard url the server wants).
@@ -227,17 +229,17 @@ If not, the server still MUST also respond to downloads at the standard url
227mentioned on the previous paragraph, to make it possible for a client 229mentioned on the previous paragraph, to make it possible for a client
228to try downloading a file on any NIP-96 compatible server by knowing just the SHA-256 file hash. 230to try downloading a file on any NIP-96 compatible server by knowing just the SHA-256 file hash.
229 231
230Note that the "\<sha256-file-hash\>" part is from the **original** file, **not** from the **transformed** file if the uploaded file went through any server transformation. 232Note that the "\<sha256-hash\>" part is from the **original** file, **not** from the **transformed** file if the uploaded file went through any server transformation.
231 233
232Supporting ".ext", meaning "file extension", is required for `servers`. It is optional, although recommended, for `clients` to append it to the path. 234Supporting ".ext", meaning "file extension", is required for `servers`. It is optional, although recommended, for `clients` to append it to the path.
233When present it may be used by `servers` to know which `Content-Type` header to send (e.g.: "Content-Type": "image/png" for ".png" extension). 235When present it may be used by `servers` to know which `Content-Type` header to send (e.g.: "Content-Type": "image/png" for ".png" extension).
234The file extension may be absent because the hash is the only needed string to uniquely identify a file. 236The file extension may be absent because the hash is the only needed string to uniquely identify a file.
235 237
236Example: `https://your-file-server.example/custom-api-path/719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b.png` 238Example: `$api_url/719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b.png`
237 239
238### Media Transformations 240### Media Transformations
239 241
240`Servers` may respond to some media transformation query parameters and ignore those they don't support by serving 242`servers` may respond to some media transformation query parameters and ignore those they don't support by serving
241the original media file without transformations. 243the original media file without transformations.
242 244
243#### Image Transformations 245#### Image Transformations
@@ -245,36 +247,75 @@ the original media file without transformations.
245##### Resizing 247##### Resizing
246 248
247Upon upload, `servers` may create resized image variants, such as thumbnails, respecting the original aspect ratio. 249Upon upload, `servers` may create resized image variants, such as thumbnails, respecting the original aspect ratio.
248`Clients` may use the `w` query parameter to request an image version with the desired pixel width. 250`clients` may use the `w` query parameter to request an image version with the desired pixel width.
249`Servers` can then serve the variant with the closest width to the parameter value 251`servers` can then serve the variant with the closest width to the parameter value
250or an image variant generated on the fly. 252or an image variant generated on the fly.
251 253
252Example: `https://your-file-server.example/custom-api-path/<sha256-file-hash>.png?w=32` 254Example: `$api_url/<sha256-hash>.png?w=32`
253 255
254## Deletion 256## Deletion
255 257
256`Servers` must make available the route `https://deletion.domain/deletion-path/<sha256-file-hash>(.ext)` (route taken from `https://your-file-server.example/.well-known/nostr/nip96.json` "api_url" field) with `DELETE` method for file deletion. 258`DELETE $api_url/<sha256-hash>(.ext)`
257 259
258Note that the "\<sha256-file-hash\>" part is from the **original** file, **not** from the **transformed** file if the uploaded file went through any server transformation. 260**AUTH required**
259 261
260The extension is optional as the file hash is the only needed file identification. 262Note that the `/<sha256-hash>` part is from the **original** file, **not** from the **transformed** file if the uploaded file went through any server transformation.
261 263
262`Clients` should send a `DELETE` request to the server deletion route in the above format. It must include a NIP-98 `Authorization` header. 264The extension is optional as the file hash is the only needed file identification.
263 265
264The `server` should reject deletes from users other than the original uploader. The `pubkey` encoded on the header value identifies the user. 266The `server` should reject deletes from users other than the original uploader with the appropriate http response code (403 Forbidden).
265 267
266It should be noted that more than one user may have uploaded the same file (with the same hash). In this case, a delete must not really delete the file but just remove the user's `pubkey` from the file owners list (considering the server keeps just one copy of the same file, because multiple uploads of the same file results 268It should be noted that more than one user may have uploaded the same file (with the same hash). In this case, a delete must not really delete the file but just remove the user's `pubkey` from the file owners list (considering the server keeps just one copy of the same file, because multiple uploads of the same file results
267in the same file hash). 269in the same file hash).
268 270
269The successful response is a 200 OK one with just basic JSON fields: 271The successful response is a 200 OK one with just basic JSON fields:
270 272
273```json
274{
275 "status": "success",
276 "message": "File deleted."
277}
271``` 278```
279
280## Listing files
281
282`GET $api_url?page=x&count=y`
283
284**AUTH required**
285
286Returns a list of files linked to the authenticated users pubkey.
287
288Example Response:
289
290```jsonc
272{ 291{
273 status: "success", 292 "count": 1, // server page size, eg. max(1, min(server_max_page_size, arg_count))
274 message: "File deleted." 293 "total": 1, // total number of files
294 "page": 0, // the current page number
295 "files": [
296 {
297 "tags": [
298 ["ox", "719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b"],
299 ["x", "5d2899290e0e69bcd809949ee516a4a1597205390878f780c098707a7f18e3df"],
300 ["size", "123456"],
301 ["alt", "a meme that makes you laugh"],
302 ["expiration", "1715691139"],
303 // ...other metadata
304 ],
305 "content": "haha funny meme", // caption
306 "created_at": 1715691130 // upload timestamp
307 }
308 ]
275} 309}
276``` 310```
277 311
312`files` contains an array of NIP-94 events
313
314### Query args
315
316- `page` page number (`offset=page*count`)
317- `count` number of items per page
318
278## Selecting a Server 319## Selecting a Server
279 320
280Note: HTTP File Storage Server developers may skip this section. This is meant for client developers. 321Note: HTTP File Storage Server developers may skip this section. This is meant for client developers.
@@ -282,14 +323,14 @@ Note: HTTP File Storage Server developers may skip this section. This is meant f
282A File Server Preference event is a kind 10096 replaceable event meant to select one or more servers the user wants 323A File Server Preference event is a kind 10096 replaceable event meant to select one or more servers the user wants
283to upload files to. Servers are listed as `server` tags: 324to upload files to. Servers are listed as `server` tags:
284 325
285```js 326```jsonc
286{ 327{
287 // ...
288 "kind": 10096, 328 "kind": 10096,
289 "content": "", 329 "content": "",
290 "tags": [ 330 "tags": [
291 ["server", "https://file.server.one"], 331 ["server", "https://file.server.one"],
292 ["server", "https://file.server.two"] 332 ["server", "https://file.server.two"]
293 ] 333 ],
334 // other fields...
294} 335}
295``` 336```