Skip to content

Commit

Permalink
Make it clear that is is an HTTP file storage server integration
Browse files Browse the repository at this point in the history
  • Loading branch information
arthurfranca committed May 23, 2023
1 parent dc8b808 commit 1a6f8d4
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 92 deletions.
91 changes: 0 additions & 91 deletions 95.md

This file was deleted.

122 changes: 122 additions & 0 deletions 96.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
NIP-96
======

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

`draft` `optional` `author:arthurfranca` `author:Semisol` `author:staab`

## Motivation

This NIP defines a simple unified REST API for HTTP file storage servers so to integrate them to the nostr network.
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 serves wishing to be accessible by nostr users should opt-in by making available
an https route at `/nip95(/<sha256-file-hash>)` for handling files as described below.

## Upload

A file can be uploaded one at a time to `https://server.domain/nip95` as `multipart/form-data` content type using `POST` method with the file object set to the `file` form data field.

`Clients` must add a [NIP-98](98.md) `Authorization` header 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.

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`: strict description text for visibility-impaired users;
- `caption`: loose description.

Others custom form data fields may be used depending on specific `server` support.
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.

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

The `server` MUST reject the upload if the `payload` tag value contains an already used SHA-256 hash (if file is already owned by the same pubkey) or it isn't the same of the received file.

The `server` SHOULD also store the header value (decoded or not) for accountability purpose as this proves that the user with the unique pubkey did ask for the upload.

The `server` MAY reject the upload if the user is not authorized for any reason such as pending payment (Although payment flow is not strictly required nor covered by this NIP. Server owners decide if the storage is free or not.)

The upload response is a json as follows:

```js
{
nip95: {
// SHA-256 hash. Empty string if unsuccessful
x: "719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b",
},
errors: {
// Empty array if successful
nip95: ['error 1', 'error 2']
}
}
```

`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 with atleast these tags:

```js
[
// Note we appended .png from the original uploaded file extension to the received `x` value
// (it is optional but extremely suggested to add the extension as it will help nostr clients detecting the file type by using regular expression)
["url", "https://server.domain/nip95/719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b.png"],
["x", "719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b"]
]
```

If the file upload was made to more than one `server`, `r` query params must be added to the url string, containing each of the `servers` used in **successful** upload operations. For example:

```js
[
["url", "https://server.domain/nip95/<sha256-file-hash>.png?r=server.domain2&r=server.domain3&r=server.domain4"],
["x", <the-hash>]
]
```

Alternativelly, instead of using NIP-94, the `client` can share or embbed on a nostr note just the above url, optionally with added [NIP-54](54.md) inline metadata such as a "magnet" field.

### File compression

File compression is optionally done at the `client`, **never on server**, before uploading.
This is done to assure the same file hash result, no matter to how many servers one may upload the same file.

## Download

`Servers` must make available the route path `/nip95/<sha256-file-hash>(.ext)` with `GET` method for file download.

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

`Clients` should send a `GET` request to the server url in the format `https://server.domain/nip95/<sha256-file-hash>.png?r=server.domain2&r=server.domain3`.

If the `server` doesn't have the file anymore, it SHOULD reply with a `302` HTTP redirect to the next `server` domain included as `r` query param. For example, issue a redirect to `https://server.domain2/nip95/<sha256-file-hash>.png?r=server.domain3`. The url used for the redirect SHOULD be limited to 3 `r` query params, so to avoid excessive number of redirects.

This redirect cooperation between `servers` is important to make a resource embedded by url in a note last longer.

## Deletion

`Servers` must make available the route path `/nip95/<sha256-file-hash>(.ext)` with `DELETE` method for file deletion.
The extension is optional as the file hash is the only needed file identification.

`Clients` should send a `DELETE` request to the server url in the format `https://server.domain/nip95/<sha256-file-hash>.png`. 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 (so 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.

The response is similar to the json sent on uploads.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
- [NIP-65: Relay List Metadata](65.md)
- [NIP-78: Application-specific data](78.md)
- [NIP-94: File Metadata](94.md)
- [NIP-95: File Storage](95.md)
- [NIP-96: HTTP File Storage Integration](96.md)

## Event Kinds

Expand Down

0 comments on commit 1a6f8d4

Please sign in to comment.