mirror of
https://github.com/studiokaiji/nostr-webhost.git
synced 2025-12-17 06:44:28 +01:00
Merge pull request #45 from studiokaiji/feature-#34-subdomain-access
Feature #34 subdomain access
This commit is contained in:
@@ -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()
|
||||
@@ -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を取得
|
||||
@@ -49,98 +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 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:
|
||||
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
}
|
||||
} else {
|
||||
ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
}
|
||||
|
||||
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 err != nil {
|
||||
ctx.String(http.StatusBadRequest, "Invalid npub")
|
||||
return
|
||||
}
|
||||
pubKey = v.(string)
|
||||
if mode == "secure" {
|
||||
filter.Authors = []string{subdomainPubKey}
|
||||
}
|
||||
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,
|
||||
})
|
||||
ev := pool.QuerySingle(ctx, allRelays, filter)
|
||||
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:
|
||||
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))
|
||||
@@ -149,5 +87,94 @@ func Start(port string) {
|
||||
return
|
||||
})
|
||||
|
||||
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.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
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
hostr/cmd/tools/getContentType.go
Normal file
19
hostr/cmd/tools/getContentType.go
Normal file
@@ -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
hostr/cmd/tools/resolvePubKey.go
Normal file
24
hostr/cmd/tools/resolvePubKey.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -17,10 +17,6 @@ import (
|
||||
var cuteOstrich string
|
||||
|
||||
func main() {
|
||||
var (
|
||||
port string
|
||||
)
|
||||
|
||||
app := &cli.App{
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
@@ -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
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user