NIP-96 sign payload, content field for file caption (#148)

* content in upload response for file caption as in NIP-94, optional signing of file payload in NIP-98 header

* Content in NIP-94 as well
This commit is contained in:
Cronus 2024-09-19 21:33:02 +07:00 committed by GitHub
parent 0b2b69529b
commit c8c295f839
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 20 additions and 4 deletions

View File

@ -51,6 +51,7 @@ type FileMetadata struct {
TorrentInfoHash string TorrentInfoHash string
Blurhash string Blurhash string
Thumb string Thumb string
Content string
} }
func (fm FileMetadata) IsVideo() bool { return strings.Split(fm.M, "/")[0] == "video" } func (fm FileMetadata) IsVideo() bool { return strings.Split(fm.M, "/")[0] == "video" }

View File

@ -3,9 +3,12 @@ package nip96
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"hash"
"io" "io"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
@ -26,6 +29,7 @@ func Upload(ctx context.Context, req UploadRequest) (*UploadResponse, error) {
} }
var requestBody bytes.Buffer var requestBody bytes.Buffer
fileHash := sha256.New()
writer := multipart.NewWriter(&requestBody) writer := multipart.NewWriter(&requestBody)
{ {
// Add the file // Add the file
@ -33,7 +37,7 @@ func Upload(ctx context.Context, req UploadRequest) (*UploadResponse, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("multipartWriter.CreateFormFile: %w", err) return nil, fmt.Errorf("multipartWriter.CreateFormFile: %w", err)
} }
if _, err := io.Copy(fileWriter, req.File); err != nil { if _, err := io.Copy(fileWriter, io.TeeReader(req.File, fileHash)); err != nil {
return nil, fmt.Errorf("io.Copy: %w", err) return nil, fmt.Errorf("io.Copy: %w", err)
} }
@ -61,7 +65,10 @@ func Upload(ctx context.Context, req UploadRequest) (*UploadResponse, error) {
uploadReq.Header.Set("Content-Type", writer.FormDataContentType()) uploadReq.Header.Set("Content-Type", writer.FormDataContentType())
if req.SK != "" { if req.SK != "" {
auth, err := generateAuthHeader(req.SK, req.Host) if !req.SignPayload {
fileHash = nil
}
auth, err := generateAuthHeader(req.SK, req.Host, fileHash)
if err != nil { if err != nil {
return nil, fmt.Errorf("generateAuthHeader: %w", err) return nil, fmt.Errorf("generateAuthHeader: %w", err)
} }
@ -99,7 +106,7 @@ func Upload(ctx context.Context, req UploadRequest) (*UploadResponse, error) {
} }
} }
func generateAuthHeader(sk, host string) (string, error) { func generateAuthHeader(sk, host string, fileHash hash.Hash) (string, error) {
pk, err := nostr.GetPublicKey(sk) pk, err := nostr.GetPublicKey(sk)
if err != nil { if err != nil {
return "", fmt.Errorf("nostr.GetPublicKey: %w", err) return "", fmt.Errorf("nostr.GetPublicKey: %w", err)
@ -114,6 +121,9 @@ func generateAuthHeader(sk, host string) (string, error) {
nostr.Tag{"method", "POST"}, nostr.Tag{"method", "POST"},
}, },
} }
if fileHash != nil {
event.Tags = append(event.Tags, nostr.Tag{"payload", hex.EncodeToString(fileHash.Sum(nil))})
}
event.Sign(sk) event.Sign(sk)
b, err := json.Marshal(event) b, err := json.Marshal(event)

View File

@ -22,6 +22,7 @@ func TestUpload(t *testing.T) {
//Host: "https://nostrcheck.me/api/v2/media", //Host: "https://nostrcheck.me/api/v2/media",
//Host: "https://nostrage.com/api/v2/media", //Host: "https://nostrage.com/api/v2/media",
SK: nostr.GeneratePrivateKey(), SK: nostr.GeneratePrivateKey(),
SignPayload: true,
File: img, File: img,
Filename: "ostrich.png", Filename: "ostrich.png",
Caption: "nostr ostrich", Caption: "nostr ostrich",

View File

@ -16,6 +16,9 @@ type UploadRequest struct {
// SK is a private key used to sign the NIP-98 Auth header. If not set // SK is a private key used to sign the NIP-98 Auth header. If not set
// the auth header will not be included in the upload. // the auth header will not be included in the upload.
SK string SK string
// Optional signing of payload (file) as described in NIP-98, if enabled
// includes `payload` tag with file's sha256 in signed event / auth header.
SignPayload bool
// File is the file to upload. // File is the file to upload.
File io.Reader File io.Reader
@ -64,6 +67,7 @@ type UploadResponse struct {
Message string `json:"message"` Message string `json:"message"`
ProcessingURL string `json:"processing_url"` ProcessingURL string `json:"processing_url"`
Nip94Event struct { Nip94Event struct {
Tags nostr.Tags `json:"tags"` Tags nostr.Tags `json:"tags"`
Content string `json:"content"`
} `json:"nip94_event"` } `json:"nip94_event"`
} }