Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NIP-96 - HTTP File Storage Integration #547

Merged
merged 51 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
b8f6ca5
Add NIP-95 - File Storage
arthurfranca May 21, 2023
cce42b8
Add missing response info
arthurfranca May 21, 2023
621f79d
Make it clear that is is an HTTP file storage server integration
arthurfranca May 23, 2023
0d60c36
Add monetization suggestion
arthurfranca May 25, 2023
6857c19
Use zap split tags for monetization suggestion
arthurfranca Jun 29, 2023
b643176
Add resize option
arthurfranca Jul 3, 2023
6b21701
Add Zap Gates Integration
arthurfranca Jul 4, 2023
30b274c
Replace /nip96 convention with /.well-known/nostr.json configuration
arthurfranca Jul 10, 2023
a8894b1
Relays can choose to also act as HTTP file storage server
arthurfranca Jul 11, 2023
c81d47a
Remove nip96 tag in favor of x tags third element
arthurfranca Jul 18, 2023
4510d93
Fix typo
arthurfranca Jul 18, 2023
fa0199c
Remove redirect cooperation
arthurfranca Jul 19, 2023
13ccda3
Replaced 422 with 400 status code
arthurfranca Jul 19, 2023
b726434
Update 96.md
arthurfranca Jul 20, 2023
b103ae8
Update 96.md
arthurfranca Jul 20, 2023
df39f92
Update 96.md
arthurfranca Jul 20, 2023
ce71b87
Update 96.md
arthurfranca Jul 20, 2023
a06141d
Update 96.md
arthurfranca Jul 20, 2023
b1b7147
Update 96.md
arthurfranca Jul 20, 2023
2a12aa2
Update 96.md
arthurfranca Jul 20, 2023
d4834be
Make file expiration a range and add terms_of_service
arthurfranca Jul 20, 2023
be79a21
Add optional content_type field
arthurfranca Jul 20, 2023
fca6ef1
Add plans and tos
arthurfranca Jul 20, 2023
688698d
Remove monetization
arthurfranca Aug 7, 2023
0547507
Apply minor fixes
arthurfranca Sep 1, 2023
6d307be
Update 96.md
arthurfranca Sep 5, 2023
2bbcdac
Fix after review
arthurfranca Sep 5, 2023
71ab9bc
Add kind 10096
arthurfranca Sep 6, 2023
3bd426d
Apply suggestions
arthurfranca Sep 14, 2023
e8c9867
Add suggestions
arthurfranca Sep 18, 2023
b554176
Remove duplicate field
arthurfranca Sep 20, 2023
c257f2f
Add optional is_nip98_required plan config
arthurfranca Sep 21, 2023
a3adb97
Add suggestions
arthurfranca Sep 21, 2023
ff62237
Replace x with ox tag for original file hash
arthurfranca Sep 27, 2023
7ef74c8
Make minor changes
arthurfranca Oct 8, 2023
c8f3a35
Remove nip96 namespace response field
arthurfranca Oct 9, 2023
0663cfa
Add note about alternative file processing flow
arthurfranca Oct 9, 2023
cc9f840
Simplify processing flow
arthurfranca Oct 11, 2023
7db19bc
Add nostrcheck to server list
arthurfranca Oct 12, 2023
2cd0bea
Add audio/* example
arthurfranca Oct 13, 2023
c2beedb
Explain what metadata to show before processing is done
arthurfranca Oct 13, 2023
b2a10d4
Add nostrage to list
arthurfranca Oct 17, 2023
5130204
Add eta
arthurfranca Oct 17, 2023
ebfb145
Add sove to list and replace eta with percentage
arthurfranca Oct 18, 2023
5dbc4c0
Fix status code
arthurfranca Oct 20, 2023
a478c71
Add nostr.build to list
arthurfranca Oct 30, 2023
2bfda96
Add sovbit
arthurfranca Oct 30, 2023
b0d5344
Add optional extra http servers to ox tag
arthurfranca Nov 14, 2023
d67c4c2
Add void.cat to list
arthurfranca Nov 20, 2023
2ed4fa6
Small fix
arthurfranca Nov 24, 2023
38c697c
Remove ox third array element
arthurfranca Dec 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions 94.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This NIP specifies the use of the `1063` event type, having in `content` a descr
* `m` a string indicating the data type of the file. The [MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) format must be used, and they should be lowercase.
* `"aes-256-gcm"` (optional) key and nonce for AES-GCM encryption with tagSize always 128bits
* `x` containing the SHA-256 hexencoded string of the file.
* `ox` containing the SHA-256 hexencoded string of the original file, before any transformations done by upload server; third tag array item is the root URL of the NIP-96 compatible HTTP file storage server used on upload.
* `size` (optional) size of file in bytes
* `dim` (optional) size of file in pixels in the form `<width>x<height>`
* `magnet` (optional) URI to magnet file
Expand All @@ -37,6 +38,7 @@ This NIP specifies the use of the `1063` event type, having in `content` a descr
["aes-256-gcm",<key>, <iv>],
["m", <MIME type>],
["x",<Hash SHA-256>],
["ox",<Hash SHA-256>, <HTTP server URL>],
["size", <size of file in bytes>],
["dim", <size of file in pixels>],
["magnet",<magnet URI> ],
Expand Down
299 changes: 299 additions & 0 deletions 96.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
NIP-96
======

HTTP File Storage Integration
-----------------------------

`draft` `optional` `author:arthurfranca` `author:Semisol` `author:staab` `author:v0l` `author:bndw` `author:michaelhall923` `author:fishcakeday` `author:quentintaranpino`

## Introduction

This NIP defines a REST API for HTTP file storage servers intended to be used in conjunction with the nostr network.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to separate "file storage" from "media storage" as a standard. File != media in many cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I'm adding "accepted_mime_types" field to /.well-known/nostr/nip96.json. The user already can send a field content_type: "" when uploading. The server can block what it doesn't support.

The main use case is for media storage but who knows the future.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea. I would suggest to make content_types and accept array of values, but to make it optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok changing it to content_types

The API will enable nostr users to upload files and later reference them by url on nostr notes.

The spec DOES NOT use regular nostr events through websockets for
storing, requesting nor retrieving data because, for simplicity, the server
will not have to learn anything about nostr relays.

## Server Adaptation

File 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`:

```js
{
// Required
// File upload and deletion are served from this url
// Also downloads if "download_url" field is absent or empty string
"api_url": "https://your-file-server.example/custom-api-path",
// Optional
// If absent, downloads are served from the api_url

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is again, not ideal. There are different dynamics involved in serving API endpoint vs. serving bulk files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"api_url" field was just to merge "delete" and "upload", depending if it is POST or DELETE action.

At this moment this NIP is just for basic functionallity (upload/delete/download one file at a time). Your server can have extra features outside of the NIP scope or we can expand it after PR merge.

Do you need me to split delete and upload to their own urls for some reason?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am mainly concerned with "Also downloads if "download_url" field is absent or empty string". API is fine, but I do not think it is ideal to have predefined download_url It is possible maybe to have one that redirects to the correct place, but not great. In many cases, when the volume of requests grows, it is possible that the files hosts want to segregate different media types across different end-points, and even for the same type. Maybe have an API that would tell where to find a file if needed? I am not sure yet myself how to best approach this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I got what you mean. We do need atleast an url to search for files by hash (so one can search at many servers till one finds it).

But what if we keep the download_url at /.well-known/nostr/nip96.json so that when trying to find a file at other servers, I can use this fixed one (or api_url when absent). BUT what if after uploading a file, the upload response includes an url to download the uploaded file? Now you can kind of load balance by sending different urls at each upload like:

Upload 1: response has url "https://your-file-server.example/path-A/hash1"
Upload 2: response has url "https://your-file-server.example/path-B/hash2"
Upload 3: response has url "https://your-file-server.example/path-C/hash3"
Upload 4: response has url "https://your-file-server.example/path-A/hash1"
and so on..

In the example you have a group of 3 different download routes you use so effectively load balancing, because the nostr events will use them instead of what is informed at the config file.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am more interested returning different hostnames based on media type, e.g., pfp, image, video, hls stream

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright. You can do it here at the "url" upload response field:

nips/96.md

Lines 147 to 150 in 5813403

// Could also be any url to download the file
// (not using /.well-known/nostr/nip96.json's "download_url" prefix),
// for load balancing purposes for example.
["url", "https://your-file-server.example/custom-api-path/719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b.png"],

It can be any hostname, any path, any filename. This url will be the one used inside nostr events to download the file.

But, for decentralization purposes, you still have to support this fixed url when the user just knows the original file hash and wanna try downloading from you:

nips/96.md

Lines 223 to 225 in 5813403

## Download
`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.

Does this solve the problem?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, thanks

"download_url": "https://a-cdn.example/a-path",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not suitable for many reasons, one being separation of endpoints based on a file type. If the target just a file and not media, then this may be OK.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

separation of endpoints based on a file type. If the target just a file and not media, then this may be OK.

There are libs able to detect file type just from the begginning of a file content like https://github.com/sindresorhus/file-type. Can't you use this to block upload of unsupported types or types that weren't able to be recognized by such a lib?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not worried about file type here, see the comment above, please. I assume we are talking about download_url here, the URL used to download the file after it was uploaded, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah sorry downloads. But it is a fixed download path that the client will just append the file hash it wants to download.

There will be also temporary download urls for cases of waiting for server media processing as @quentintaranpino just suggested. But these will be inserted in the upload response instead of in this config file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will expand this topic at your conversation below.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For file processing, I’d suggest to return URL immediately with original file served but short cache-control, and then swap the file and extend cache-control as needed. This is a common practice where we need for the user to have immediate access to the media. It’s still OK to return IOU url, but makes it hard for the client to implement. So, if we need to process a video, receive upload and start serving it as the final file but limit cache to only few minutes. Once processing is done, start serving the processed file and extend cache headers as needed.

// Optional
// Note: This field is not meant to be set by HTTP Servers.
// Use this if you are a nostr relay using your /.well-known/nostr/nip96.json
// just to redirect to someone else's http file storage server's /.well-known/nostr/nip96.json
// In this case, "api_url" field must be an empty string
"delegated_to_url": "https://your-file-server.example",
// Optional
"supported_nips": [60],
// Optional
"tos_url": "https://your-file-server.example/terms-of-service",
// Optional
"content_types": ["image/jpeg", "video/webm"]
Copy link

@fishcakeday fishcakeday Oct 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume wildcards can be used here, e.g., image/*, video/*, audio/* right?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my point of view it is correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add image/* to the example

// Optional
"plans": {
// "free" is the only standardized plan key and
// clients may use its presence to learn if server offers free storage
"free": {
"name": "Free Tier",
// Default is true
// All plans MUST support NIP-98 uploads
// but some plans may also allow uploads without it
"is_nip98_required": true,
"url": "https://...", // plan's landing page if there is one
"max_byte_size": 10485760,
// Range in days / 0 for no expiration
// [7, 0] means it may vary from 7 days to unlimited persistence,
// [0, 0] means it has no expiration
// early expiration may be due to low traffic or any other factor
"file_expiration": [14, 90],
"media_transformations": {
"image": [
'resizing'
]
}
}
}
}
```

### Relay Hints
arthurfranca marked this conversation as resolved.
Show resolved Hide resolved

Note: This section is not meant to be used by HTTP Servers.

A nostr relay MAY redirect to someone else's HTTP file storage server by
adding a `/.well-known/nostr/nip96.json` with "delegated_to_url" field
pointing to the url where the server hosts its own
`/.well-known/nostr/nip96.json`. In this case, the "api_url" field must
be an empty string and all other fields must be absent.

If the nostr relay is also an HTTP file storage server,
it must use the "api_url" field instead.

### List of Supporting File Storage Servers

| Name | Domain |
| ---------- | --------------------- |
| nostrcheck | https://nostrcheck.me |

## Upload

A 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.
arthurfranca marked this conversation as resolved.
Show resolved Hide resolved

`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).
If using an html form, use an `Authorization` form data field instead.
arthurfranca marked this conversation as resolved.
Show resolved Hide resolved

These following **optional** form data fields MAY be used by `servers` and SHOULD be sent by `clients`:
- `expiration`: string of the UNIX timestamp in seconds. Empty string if file should be stored forever. The server isn't required to honor this;
- `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;
- `alt`: (recommended) strict description text for visibility-impaired users;
- `caption`: loose description;
- `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;
- `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.


Others custom form data fields may be used depending on specific `server` support.
arthurfranca marked this conversation as resolved.
Show resolved Hide resolved
The `server` isn't required to store any metadata sent by `clients`.

Note 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.
arthurfranca marked this conversation as resolved.
Show resolved Hide resolved

The `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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest standardizing the file naming structure here, if you want to target decentralization. SHA-256 of the file content (before any alterations) seems to be the good starting point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be possible with the x tag appended to the url (with NIP-54) or with the x tag (with NIP-94).
We can't use the filename because other file urls not using NIP-96 most times won't use the convention of having the SHA-256 as filename.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I understood your reply correctly. Can we not specify that the server must have ways to access the file by its hash?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHA-256 of the file content (before any alterations) seems to be the good starting point.

In practice that's what this PR wants. A client must be able to download at https://download.route/path/<original-file-hash>. In the future when the file gets deleted, a client can try another server's download route appending the original file's hash as if it was the filename.

The hash is enough to uniquely identify a file, that's why it will be used on the "download" and "delete" routes.

The `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.
Note 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.
It 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).

The `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.

The `server` MUST reject with 413 Payload Too Large if file size exceeds limits.

The `server` MUST reject with 400 Bad Request status if some fields are invalid.

The `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.

The `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.).

The `server` MUST reply with 200 OK HTTP status code on successful uploads.

The upload response is a json object as follows:

```js
{
// "success" if successful or "error" if not
status: "success",
// Free text success, failure or info message
message: "Upload successful.",
// Optional. See "Delayed Processing" section
processing_url: "...",
// This uses the NIP-94 event format but DO NOT need
// to fill some fields like "id", "pubkey", "created_at" and "sig"
//
// This holds the download url ("url"),
// the ORIGINAL file hash before server transformations ("ox")
// and, optionally, all file metadata the server wants to make available
//
// nip94_event field is absent if unsuccessful upload
nip94_event: {
// Required tags: "url" and "ox"
tags: [
// Can be same from /.well-known/nostr/nip96.json's "download_url" field
// (or "api_url" field if "download_url" is absent or empty) with appended
// original file hash.
//
// Note we appended .png file extension to the `x` value
// (it is optional but extremely recommended to add the extension as it will help nostr clients
// with detecting the file type by using regular expression)
//
// Could also be any url to download the file
// (using or not using the /.well-known/nostr/nip96.json's "download_url" prefix),
// for load balancing purposes for example.
["url", "https://your-file-server.example/custom-api-path/719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b.png"],
// SHA-256 hash of the ORIGINAL file, before transformations.
// The server MUST store it even though it represents the ORIGINAL file because
// users may try to download the transformed file using this value
[
"ox",
"719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b",
// Server hostname where one can find the
// /.well-known/nostr/nip96.json config resource.
//
// This value is an important hint that clients can use
// to find new NIP-96 compatible file storage servers.
"https://your-file-server.example"
],
// Optional. SHA-256 hash of the saved file after any server transformations.
// The server can but does not need to store this value.
["x", "543244319525d9d08dd69cb716a18158a249b7b3b3ec4bbde5435543acb34443"],
// Optional. Recommended for helping clients to easily know file type before downloading it.
["m", "image/png"]
// Optional. Recommended for helping clients to reserve an adequate UI space to show the file before downloading it.
["dim", "800x600"]
// ... other optional NIP-94 tags
],
content: ""
},
// ... other custom fields (please consider adding them to this NIP or to NIP-94 tags)
}
```

Note 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.

`Clients` may upload the same file to one or many `servers`.
After 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.

Alternatively, instead of using NIP-94, the `client` can share or embed on a nostr note just the above url with added "ox" [NIP-54](54.md) inline metadata field and optionally other ones.

### Delayed Processing

Sometimes the server may want to place the uploaded file in a processing queue for deferred file processing.

In that case, the server MUST serve the original file while the processing isn't done, then swap the original file for the processed one when the processing is over. The upload response is the same as usual but some optional metadata like `nip94_event.tags.*.x` and `nip94_event.tags.*.size` won't be available.

The upload response MAY include a `processing_url` field informing a temporary url that may be used by clients to check if
the file processing is done.

If the processing isn't done, the server should reply at the `processing_url` url with **404 Not Found** and the following JSON:

```
{
// It should be "processing". If "error" it would mean the processing failed.
status: "processing",
message: "Processing. Please check again later for updated status."
}
```

When the processing is over, the server replies at the `processing_url` url with a regular successful 200 OK status JSON response already mentioned before (now without a "processing_url" field), possibly including optional metadata at `nip94_event.tags.*` fields
that weren't available before processing.

### File compression

File compression and other transformations like metadata stripping can be applied by the server.
However, for all file actions, such as download and deletion, the **original** file SHA-256 hash is what identifies the file in the url string.

## Download

`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.

The primary file download url informed at the upload's response field `nip94_event.tags.*.url`
can be that or not (it can be any non-standard url the server wants).
If not, the server still MUST also respond to downloads at the standard url
mentioned on the previous paragraph, to make it possible for a client
to try downloading a file on any NIP-96 compatible server by knowing just the SHA-256 file hash.

Note 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.

Supporting ".ext", meaning "file extension", is required for `servers`. It is optional, although recommended, for `clients` to append it to the path.
When present it may be used by `servers` to know which `Content-Type` header to send (e.g.: "Content-Type": "image/png" for ".png" extension).
The file extension may be absent because the hash is the only needed string to uniquely identify a file.

Example: `https://your-file-server.example/custom-api-path/719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b.png`

### Media Transformations

`Servers` may respond to some media transformation query parameters and ignore those they don't support by serving
the original media file without transformations.

#### Image Transformations

##### Resizing

Upon upload, `servers` may create resized image variants, such as thumbnails, respecting the original aspect ratio.
`Clients` may use the `w` query parameter to request an image version with the desired pixel width.
`Servers` can then serve the variant with the closest width to the parameter value
or an image variant generated on the fly.

arthurfranca marked this conversation as resolved.
Show resolved Hide resolved
Example: `https://your-file-server.example/custom-api-path/<sha256-file-hash>.png?w=32`

## Deletion

`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.

Note 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.

The extension is optional as the file hash is the only needed file identification.

`Clients` should send a `DELETE` request to the server deletion route in the above format. It must include a NIP-98 `Authorization` header.

The `server` should reject deletes from users other than the original uploader. The `pubkey` encoded on the header value identifies the user.

It 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
in the same file hash).

The successfull response is a 200 OK one with just basic JSON fields:

```
{
status: "success",
message: "File deleted."
}
```

## Selecting a Server

Note: HTTP File Storage Server developers may skip this section. This is meant for client developers.

A File Server Preference event is a kind 10096 replaceable event meant to select one or more servers the user wants
to upload files to. Servers are listed as `server` tags:

```js
{
// ...
"kind": 10096,
"content": "",
"tags": [
["server", "https://file.server.one"],
["server", "https://file.server.two"]
]
}
```
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
- [NIP-78: Application-specific data](78.md)
- [NIP-89: Recommended Application Handlers](89.md)
- [NIP-94: File Metadata](94.md)
- [NIP-96: HTTP File Storage Integration](96.md)
- [NIP-98: HTTP Auth](98.md)
- [NIP-99: Classified Listings](99.md)

Expand Down