-
Notifications
You must be signed in to change notification settings - Fork 578
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make it clear that is is an HTTP file storage server integration
- Loading branch information
1 parent
dc8b808
commit 1a6f8d4
Showing
3 changed files
with
123 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters