Skip to content

Commit

Permalink
Merge pull request #45 from studiokaiji/feature-#34-subdomain-access
Browse files Browse the repository at this point in the history
Feature #34 subdomain access
  • Loading branch information
studiokaiji authored Oct 22, 2023
2 parents ff3b53d + bb283c7 commit 381b11f
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 90 deletions.
187 changes: 107 additions & 80 deletions hostr/cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ package server

import (
"context"
"encoding/base64"
"net/http"
"strings"

"github.com/gin-gonic/gin"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
"github.com/studiokaiji/nostr-webhost/hostr/cmd/consts"
"github.com/studiokaiji/nostr-webhost/hostr/cmd/relays"
"github.com/studiokaiji/nostr-webhost/hostr/cmd/tools"
)

func Start(port string) {
func Start(port string, mode string) {
ctx := context.Background()

allRelays, err := relays.GetAllRelays()
Expand All @@ -27,6 +28,19 @@ func Start(port string) {
r.GET("/e/:hex_or_nevent", func(ctx *gin.Context) {
hexOrNevent := ctx.Param("hex_or_nevent")

subdomainPubKey := ""

if mode == "secure" {
// modeがsecureの場合、サブドメインにnpubが含まれていないルーティングは許可しない
host := ctx.Request.Host
subdomain := strings.Split(host, ".")[0]
subdomainPubKey, err = tools.ResolvePubKey(subdomain)
if err != nil {
ctx.String(http.StatusBadRequest, "Routing without npub in the subdomain is not allowed")
return
}
}

ids := []string{}

// neventからIDを取得
Expand All @@ -49,48 +63,22 @@ func Start(port string) {
ids = append(ids, hexOrNevent)
}

// Poolからデータを取得する
ev := pool.QuerySingle(ctx, allRelays, nostr.Filter{
filter := nostr.Filter{
Kinds: []int{consts.KindWebhostHTML, consts.KindWebhostCSS, consts.KindWebhostJS, consts.KindWebhostPicture},
IDs: ids,
})
}
if mode == "secure" {
filter.Authors = []string{subdomainPubKey}
}

// Poolからデータを取得する
ev := pool.QuerySingle(ctx, allRelays, filter)
if ev != nil {
switch ev.Kind {
case consts.KindWebhostHTML:
ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(ev.Content))
case consts.KindWebhostCSS:
ctx.Data(http.StatusOK, "text/css; charset=utf-8", []byte(ev.Content))
case consts.KindWebhostJS:
ctx.Data(http.StatusOK, "text/javascript; charset=utf-8", []byte(ev.Content))
case consts.KindWebhostPicture:
{
eTag := ev.Tags.GetFirst([]string{"e"})
mTag := ev.Tags.GetFirst([]string{"m"})

if eTag == nil || mTag == nil {
ctx.String(http.StatusBadRequest, http.StatusText((http.StatusBadRequest)))
return
}

evData := pool.QuerySingle(ctx, allRelays, nostr.Filter{
IDs: []string{eTag.Value()},
})

if evData == nil {
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
return
}

data, err := base64.StdEncoding.DecodeString(evData.Content)
if err != nil {
ctx.String(http.StatusBadRequest, http.StatusText((http.StatusBadRequest)))
return
}

ctx.Data(http.StatusOK, mTag.Value(), data)
}
default:
contentType, err := tools.GetContentType(ev.Kind)
if err != nil {
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
} else {
ctx.Data(http.StatusOK, contentType, []byte(ev.Content))
}
} else {
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
Expand All @@ -99,55 +87,94 @@ func Start(port string) {
return
})

// Replaceable Event (NIP-33)
r.GET("/p/:pubKey/d/*dTag", func(ctx *gin.Context) {
// pubKeyを取得しFilterに追加
pubKey := ctx.Param("pubKey")
// npubから始まる場合はデコードする
if pubKey[0:4] == "npub" {
_, v, err := nip19.Decode(pubKey)
if mode != "secure" {
r.GET("/p/:pubKey/d/*dTag", func(ctx *gin.Context) {
// pubKeyを取得しFilterに追加
pubKey := ctx.Param("pubKey")
pubKey, err := tools.ResolvePubKey(pubKey)
if err != nil {
ctx.String(http.StatusBadRequest, "Invalid npub")
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
return
}
pubKey = v.(string)
}
authors := []string{pubKey}

// dTagを取得しFilterに追加
// dTagの最初は`/`ではじまるのでそれをslice
dTag := ctx.Param("dTag")[1:]

tags := nostr.TagMap{}
tags["d"] = []string{dTag}
authors := []string{pubKey}

// dTagを取得しFilterに追加
// dTagの最初は`/`ではじまるのでそれをslice
dTag := ctx.Param("dTag")[1:]

tags := nostr.TagMap{}
tags["d"] = []string{dTag}

// Poolからデータを取得する
ev := pool.QuerySingle(ctx, allRelays, nostr.Filter{
Kinds: []int{
consts.KindWebhostReplaceableHTML,
consts.KindWebhostReplaceableCSS,
consts.KindWebhostReplaceableJS,
},
Authors: authors,
Tags: tags,
})
if ev != nil {
contentType, err := tools.GetContentType(ev.Kind)
if err != nil {
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
ctx.Data(http.StatusOK, contentType, []byte(ev.Content))
} else {
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
}

// Poolからデータを取得する
ev := pool.QuerySingle(ctx, allRelays, nostr.Filter{
Kinds: []int{
consts.KindWebhostReplaceableHTML,
consts.KindWebhostReplaceableCSS,
consts.KindWebhostReplaceableJS,
},
Authors: authors,
Tags: tags,
return
})
if ev != nil {
switch ev.Kind {
case consts.KindWebhostReplaceableHTML:
ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(ev.Content))
case consts.KindWebhostReplaceableCSS:
ctx.Data(http.StatusOK, "text/css; charset=utf-8", []byte(ev.Content))
case consts.KindWebhostReplaceableJS:
ctx.Data(http.StatusOK, "text/javascript; charset=utf-8", []byte(ev.Content))
default:

}

if mode != "normal" {
r.GET("/d/*dTag", func(ctx *gin.Context) {
host := ctx.Request.Host
subdomain := strings.Split(host, ".")[0]

// subdomainからpubKeyを取得しFilterに追加
pubKey, err := tools.ResolvePubKey(subdomain)
if err != nil {
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
return
}
} else {
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
}

return
})
authors := []string{pubKey}

// dTagを取得しFilterに追加
// dTagの最初は`/`ではじまるのでそれをslice
dTag := ctx.Param("dTag")[1:]

tags := nostr.TagMap{}
tags["d"] = []string{dTag}

// Poolからデータを取得する
ev := pool.QuerySingle(ctx, allRelays, nostr.Filter{
Kinds: []int{
consts.KindWebhostReplaceableHTML,
consts.KindWebhostReplaceableCSS,
consts.KindWebhostReplaceableJS,
},
Authors: authors,
Tags: tags,
})
if ev != nil {
contentType, err := tools.GetContentType(ev.Kind)
if err != nil {
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
ctx.Data(http.StatusOK, contentType, []byte(ev.Content))
} else {
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
}

return
})
}

r.Run(":" + port)
}
19 changes: 19 additions & 0 deletions hostr/cmd/tools/getContentType.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package tools

import (
"fmt"

"github.com/studiokaiji/nostr-webhost/hostr/cmd/consts"
)

func GetContentType(kind int) (string, error) {
if kind == consts.KindWebhostHTML || kind == consts.KindWebhostReplaceableHTML {
return "text/html; charset=utf-8", nil
} else if kind == consts.KindWebhostCSS || kind == consts.KindWebhostReplaceableCSS {
return "text/css; charset=utf-8", nil
} else if kind == consts.KindWebhostJS || kind == consts.KindWebhostReplaceableJS {
return "text/javascript; charset=utf-8", nil
} else {
return "", fmt.Errorf("Invalid Kind")
}
}
24 changes: 24 additions & 0 deletions hostr/cmd/tools/resolvePubKey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package tools

import (
"fmt"

"github.com/nbd-wtf/go-nostr/nip19"
)

func ResolvePubKey(npubOrHex string) (string, error) {
// npubから始まる場合はデコードする
if npubOrHex[0:4] == "npub" {
_, v, err := nip19.Decode(npubOrHex)
if err != nil {
return "", fmt.Errorf("Invalid npub")
}
return v.(string), nil
} else {
_, err := nip19.EncodePublicKey(npubOrHex)
if err != nil {
return "", fmt.Errorf("Invalid pubkey")
}
}
return npubOrHex, nil
}
29 changes: 19 additions & 10 deletions hostr/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ import (
var cuteOstrich string

func main() {
var (
port string
)

app := &cli.App{
Commands: []*cli.Command{
{
Expand Down Expand Up @@ -143,15 +139,28 @@ func main() {
Usage: "🕺 Wake up web server",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "port",
Aliases: []string{"p"},
Value: "3000",
Usage: "Web server port",
Destination: &port,
Name: "port",
Aliases: []string{"p"},
Value: "3000",
Usage: "Web server port",
},
&cli.StringFlag{
Name: "mode",
Aliases: []string{"m"},
Value: "normal",
Usage: "🧪 Experimental: Enabled subdomain-based access in replaceable events.",
Action: func(ctx *cli.Context, v string) error {
if v != "normal" && v != "hybrid" && v != "secure" {
return fmt.Errorf("Invalid mode flag. Must be 'normal', 'hybrid', or 'secure'.")
}
return nil
},
},
},
Action: func(ctx *cli.Context) error {
server.Start(port)
port := ctx.String("port")
mode := ctx.String("mode")
server.Start(port, mode)
return nil
},
},
Expand Down

0 comments on commit 381b11f

Please sign in to comment.