working jwt token with expiry on event upload
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -25,7 +25,7 @@ node_modules/**
|
||||
**/node_modules
|
||||
**/node_modules/
|
||||
**/node_modules/**
|
||||
|
||||
/test*
|
||||
# and others
|
||||
/go.work.sum
|
||||
/secp256k1/
|
||||
|
||||
@@ -21,9 +21,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
issuer = "NOSTR_PUBLIC_KEY"
|
||||
secEnv = "NOSTR_SECRET_KEY"
|
||||
jwtSecEnv = "NOSTR_JWT_SECRET"
|
||||
jwtIssuerEnv = "NOSTR_PUBLIC_KEY"
|
||||
secEnv = "NOSTR_SECRET_KEY"
|
||||
jwtSecEnv = "NOSTR_JWT_SECRET"
|
||||
)
|
||||
|
||||
var userAgent = fmt.Sprintf("nostrjwt/%s", realy_lol.Version)
|
||||
@@ -67,7 +67,7 @@ nostrjwt bearer <request URL> [<optional expiry in 0h0m0s format for JWT token>]
|
||||
expiry sets an amount of time after the current moment that the token
|
||||
will expire
|
||||
|
||||
`, kind.JWTBinding.K, jwtSecEnv, issuer, jwtSecEnv)
|
||||
`, kind.JWTBinding.K, jwtSecEnv, jwtIssuerEnv, jwtSecEnv)
|
||||
os.Exit(0)
|
||||
}
|
||||
var err error
|
||||
@@ -95,7 +95,7 @@ nostrjwt bearer <request URL> [<optional expiry in 0h0m0s format for JWT token>]
|
||||
}
|
||||
fmt.Printf("%s\n%s\n", pemSec, pemPub)
|
||||
fmt.Printf("export %s=%s\n", jwtSecEnv, x509sec)
|
||||
fmt.Printf("export %s=%s\n\n", issuer, pub)
|
||||
fmt.Printf("export %s=%s\n\n", jwtIssuerEnv, pub)
|
||||
var ev event.T
|
||||
httpauth.MakeJWTEvent(string(x509pub))
|
||||
ev.Tags = tags.New(tag.New([]byte("J"), x509pub, []byte("ES256")))
|
||||
@@ -104,7 +104,7 @@ nostrjwt bearer <request URL> [<optional expiry in 0h0m0s format for JWT token>]
|
||||
if err = ev.Sign(sign); chk.E(err) {
|
||||
fail(err.Error())
|
||||
}
|
||||
fmt.Printf("%s\n", ev.Serialize())
|
||||
fmt.Printf("Nostr %s\n", ev.Serialize())
|
||||
|
||||
case "bearer":
|
||||
// check args
|
||||
@@ -118,6 +118,10 @@ nostrjwt bearer <request URL> [<optional expiry in 0h0m0s format for JWT token>]
|
||||
if len(jwtSec) == 0 {
|
||||
fail("no key found in environment variable %s", jwtSecEnv)
|
||||
}
|
||||
jwtIss := os.Getenv(jwtIssuerEnv)
|
||||
if len(jwtIss) == 0 {
|
||||
fail("no pubkey found in environment variable %s", jwtIssuerEnv)
|
||||
}
|
||||
if jskb, err = base64.URLEncoding.DecodeString(jwtSec); chk.E(err) {
|
||||
fail(err.Error())
|
||||
}
|
||||
@@ -126,13 +130,14 @@ nostrjwt bearer <request URL> [<optional expiry in 0h0m0s format for JWT token>]
|
||||
fail(err.Error())
|
||||
}
|
||||
var tok []byte
|
||||
log.I.S(os.Args)
|
||||
// generate claim
|
||||
if len(os.Args) < 5 {
|
||||
if tok, err = httpauth.GenerateJWTClaims(os.Args[2], os.Args[3]); chk.E(err) {
|
||||
if len(os.Args) == 3 {
|
||||
if tok, err = httpauth.GenerateJWTClaims(os.Args[2], jwtIss); chk.E(err) {
|
||||
fail(err.Error())
|
||||
}
|
||||
} else if len(os.Args) > 4 {
|
||||
if tok, err = httpauth.GenerateJWTClaims(os.Args[2], os.Args[3], os.Args[4]); chk.E(err) {
|
||||
} else if len(os.Args) == 4 {
|
||||
if tok, err = httpauth.GenerateJWTClaims(os.Args[2], jwtIss, os.Args[3]); chk.E(err) {
|
||||
fail(err.Error())
|
||||
}
|
||||
}
|
||||
@@ -141,7 +146,7 @@ nostrjwt bearer <request URL> [<optional expiry in 0h0m0s format for JWT token>]
|
||||
if signed, err = httpauth.SignJWTtoken(tok, sec); chk.E(err) {
|
||||
fail(err.Error())
|
||||
}
|
||||
fmt.Printf("%s", signed)
|
||||
fmt.Printf("Bearer %s\n", signed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func MonitorResources(c context.T) {
|
||||
tick := time.NewTicker(time.Minute)
|
||||
tick := time.NewTicker(time.Minute * 15)
|
||||
log.I.Ln("running process", os.Args[0], os.Getpid())
|
||||
// memStats := &runtime.MemStats{}
|
||||
for {
|
||||
|
||||
@@ -2,6 +2,7 @@ package event
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"realy.lol/ec/schnorr"
|
||||
@@ -98,6 +99,24 @@ func (ev *T) marshalWithWhitespace(dst []byte, on bool) (b []byte) {
|
||||
func Marshal(ev *T, dst []byte) (b []byte) { return ev.Marshal(dst) }
|
||||
|
||||
func (ev *T) Unmarshal(b []byte) (r []byte, err error) {
|
||||
// this parser does not cope with whitespaces in valid places in json, so we
|
||||
// scan first for linebreaks, as these indicate that it is probably not gona work and fall back to json.Unmarshal
|
||||
for _, v := range b {
|
||||
if v == '\n' {
|
||||
// revert to json.Unmarshal
|
||||
var j J
|
||||
if err = json.Unmarshal(b, &j); chk.E(err) {
|
||||
return
|
||||
}
|
||||
var e *T
|
||||
if e, err = j.ToEvent(); chk.E(err) {
|
||||
return
|
||||
}
|
||||
*ev = *e
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
key := make([]byte, 0, 9)
|
||||
r = b
|
||||
for ; len(r) > 0; r = r[1:] {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
@@ -83,7 +85,7 @@ func GenerateJWTKeys() (x509sec, x509pub, pemSec, pemPub []byte, sk *ecdsa.Priva
|
||||
return
|
||||
}
|
||||
|
||||
func GenerateJWTClaims(issuer, ur string,
|
||||
func GenerateJWTClaims(ur, issuer string,
|
||||
exp ...string) (tok []byte, err error) {
|
||||
// generate claim
|
||||
claim := &JWT{
|
||||
@@ -142,8 +144,13 @@ func GenerateAndSignJWTtoken(issuer, ur, exp, sec string) (bearer string, err er
|
||||
// that matches the signature on the JWT token.
|
||||
type VerifyJWTFunc func(npub string) (jwtPub string, pk []byte, err error)
|
||||
|
||||
// VerifyJWTtoken checks that the claims and signature on a JWT token are valid,
|
||||
// and returns the public key to check the signer matches with a nostr npub
|
||||
// issuer.
|
||||
//
|
||||
// If there is an expiry, it only checks that the token's URL is the same as the
|
||||
// prefix of the URL being verified for.
|
||||
func VerifyJWTtoken(entry, URL string, vfn VerifyJWTFunc) (pk []byte, valid bool, err error) {
|
||||
|
||||
var token *jwt.Token
|
||||
if token, err = jwt.Parse(entry, func(token *jwt.Token) (ifc interface{}, err error) {
|
||||
var iss string
|
||||
@@ -162,12 +169,6 @@ func VerifyJWTtoken(entry, URL string, vfn VerifyJWTFunc) (pk []byte, valid bool
|
||||
if jpk, err = x509.ParsePKIXPublicKey(pkb); chk.E(err) {
|
||||
return
|
||||
}
|
||||
ifc = jpk
|
||||
var sub string
|
||||
if sub, err = token.Claims.GetSubject(); sub != URL {
|
||||
err = errors.Wrap(jwt.ErrTokenInvalidClaims, "subject doesn't match expected URL")
|
||||
return
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
var exp *jwt.NumericDate
|
||||
if exp, err = token.Claims.GetExpirationTime(); chk.E(err) {
|
||||
@@ -192,6 +193,25 @@ func VerifyJWTtoken(entry, URL string, vfn VerifyJWTFunc) (pk []byte, valid bool
|
||||
}
|
||||
}
|
||||
|
||||
var sub string
|
||||
if sub, err = token.Claims.GetSubject(); chk.E(err) {
|
||||
err = errors.Wrap(jwt.ErrTokenInvalidClaims, err.Error())
|
||||
return
|
||||
}
|
||||
// when expiry is present the URL only needs to match on a prefix (already checked)
|
||||
if exp != nil {
|
||||
if !strings.HasPrefix(URL, sub) {
|
||||
log.I.S(URL, sub)
|
||||
err = errors.Wrap(jwt.ErrTokenInvalidClaims,
|
||||
fmt.Sprintf("subject doesn't match expected URL prefix for an expiring token %s != %s", sub, URL))
|
||||
return
|
||||
}
|
||||
} else if sub != URL {
|
||||
err = errors.Wrap(jwt.ErrTokenInvalidClaims, "subject doesn't match expected URL")
|
||||
return
|
||||
}
|
||||
ifc = jpk
|
||||
|
||||
return
|
||||
}, jwt.WithoutClaimsValidation()); chk.E(err) {
|
||||
return
|
||||
|
||||
@@ -37,7 +37,7 @@ func TestSignJWTtoken_VerifyJWTtoken(t *testing.T) {
|
||||
}
|
||||
spub := base64.URLEncoding.EncodeToString(spkb)
|
||||
var tok []byte
|
||||
if tok, err = GenerateJWTClaims(pub, "https://example.com", "1h"); chk.E(err) {
|
||||
if tok, err = GenerateJWTClaims("https://example.com", pub, "1h"); chk.E(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var entry string
|
||||
@@ -47,7 +47,7 @@ func TestSignJWTtoken_VerifyJWTtoken(t *testing.T) {
|
||||
vfn := func(npub string) (jwtPub string, pk []byte, err error) {
|
||||
// pubkey in token claims must match what we just put in it
|
||||
if npub != pub {
|
||||
err = fmt.Errorf("invalid jwt token npub")
|
||||
err = fmt.Errorf("invalid jwt token npub, got %s expected %s", npub, pub)
|
||||
return
|
||||
}
|
||||
pk = sign.Pub()
|
||||
|
||||
@@ -18,12 +18,12 @@ import (
|
||||
// A VerifyJWTFunc should be provided in order to search the event store for a
|
||||
// kind 13004 with a JWT signer pubkey that is granted authority for the request.
|
||||
func CheckAuth(r *http.Request, vfn VerifyJWTFunc) (valid bool, pubkey []byte, err error) {
|
||||
log.I.F("validating auth %v", vfn)
|
||||
val := r.Header.Get(HeaderKey)
|
||||
if val == "" {
|
||||
err = errorf.E("'%s' key missing from request header", HeaderKey)
|
||||
return
|
||||
}
|
||||
log.I.F("validating auth '%s'", val)
|
||||
switch {
|
||||
case strings.HasPrefix(val, NIP98Prefix):
|
||||
split := strings.Split(val, " ")
|
||||
|
||||
12
ints/ints.go
12
ints/ints.go
@@ -2,6 +2,7 @@ package ints
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"io"
|
||||
)
|
||||
|
||||
// run this to regenerate (pointlessly) the base 10 array of 4 places per entry
|
||||
@@ -87,6 +88,17 @@ func (n *T) Unmarshal(b []byte) (r []byte, err error) {
|
||||
n.N = 0
|
||||
return
|
||||
}
|
||||
// skip non-number characters
|
||||
for i, v := range b {
|
||||
if v >= '0' && v <= '9' {
|
||||
b = b[i:]
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(b) == 0 {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
// count the digits
|
||||
for ; sLen < len(b) && b[sLen] >= zero && b[sLen] <= nine && b[sLen] != ','; sLen++ {
|
||||
}
|
||||
|
||||
@@ -67,5 +67,6 @@ func (s *Server) addEvent(c context.T, rl relay.I, ev *event.T,
|
||||
}
|
||||
s.listeners.NotifyListeners(authRequired, ev)
|
||||
accepted = true
|
||||
log.I.F("event id %0x stored", ev.ID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ func (s *Server) JWTVerifyFunc(npub string) (jwtPub string, pk []byte, err error
|
||||
npub, ev.SerializeIndented())
|
||||
return
|
||||
}
|
||||
pk = ev.PubKey
|
||||
jwtPub = string(jtag.F()[0].Value())
|
||||
return
|
||||
}
|
||||
|
||||
218
realy/http-event.go
Normal file
218
realy/http-event.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package realy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/danielgtaylor/huma/v2"
|
||||
|
||||
"realy.lol/context"
|
||||
"realy.lol/event"
|
||||
"realy.lol/filter"
|
||||
"realy.lol/hex"
|
||||
"realy.lol/httpauth"
|
||||
"realy.lol/ints"
|
||||
"realy.lol/kind"
|
||||
"realy.lol/relay"
|
||||
"realy.lol/sha256"
|
||||
"realy.lol/tag"
|
||||
)
|
||||
|
||||
type EventPost struct{ *Server }
|
||||
|
||||
func NewEventPost(s *Server) (ep *EventPost) {
|
||||
return &EventPost{Server: s}
|
||||
}
|
||||
|
||||
type EventInput struct {
|
||||
RawBody []byte
|
||||
Auth string `header:"Authorization"`
|
||||
}
|
||||
|
||||
type EventOutput struct{ Body string }
|
||||
|
||||
func (ep *EventPost) RegisterEventPost(api huma.API) {
|
||||
name := "Event"
|
||||
description := "Submit an event"
|
||||
path := "/event"
|
||||
scopes := []string{"write"}
|
||||
method := http.MethodPost
|
||||
huma.Register(api, huma.Operation{
|
||||
OperationID: name,
|
||||
Summary: name,
|
||||
Path: path,
|
||||
Method: method,
|
||||
Tags: []string{"events"},
|
||||
Description: generateDescription(description, scopes),
|
||||
Security: []map[string][]string{{"auth": scopes}},
|
||||
}, func(ctx context.T, input *EventInput) (wgh *EventOutput, err error) {
|
||||
log.I.S(ctx)
|
||||
r := ctx.Value("http-request").(*http.Request)
|
||||
w := ctx.Value("http-response").(http.ResponseWriter)
|
||||
rr := GetRemoteFromReq(r)
|
||||
log.I.S(r.RemoteAddr, rr)
|
||||
ev := &event.T{}
|
||||
if _, err = ev.Unmarshal(input.RawBody); chk.E(err) {
|
||||
err = huma.Error406NotAcceptable(err.Error())
|
||||
return
|
||||
}
|
||||
var ok bool
|
||||
s := ep.Server
|
||||
sto := s.relay.Storage()
|
||||
advancedDeleter, _ := sto.(relay.AdvancedDeleter)
|
||||
var valid bool
|
||||
var pubkey []byte
|
||||
if valid, pubkey, err = httpauth.CheckAuth(r, s.JWTVerifyFunc); chk.E(err) {
|
||||
return
|
||||
}
|
||||
if !valid {
|
||||
// pubkey = ev.PubKey
|
||||
err = huma.Error401Unauthorized(
|
||||
fmt.Sprintf("invalid: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
c := context.Bg()
|
||||
accept, notice, after := s.relay.AcceptEvent(c, ev, r, rr, pubkey)
|
||||
if !accept {
|
||||
err = huma.Error401Unauthorized(notice)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(ev.GetIDBytes(), ev.ID) {
|
||||
err = huma.Error400BadRequest("event id is computed incorrectly")
|
||||
return
|
||||
}
|
||||
if ok, err = ev.Verify(); chk.T(err) {
|
||||
err = huma.Error400BadRequest("failed to verify signature")
|
||||
return
|
||||
} else if !ok {
|
||||
err = huma.Error400BadRequest("signature is invalid")
|
||||
return
|
||||
}
|
||||
storage := s.relay.Storage()
|
||||
if storage == nil {
|
||||
panic("no event store has been set to store event")
|
||||
}
|
||||
if ev.Kind.K == kind.Deletion.K {
|
||||
log.I.F("delete event\n%s", ev.Serialize())
|
||||
for _, t := range ev.Tags.Value() {
|
||||
var res []*event.T
|
||||
if t.Len() >= 2 {
|
||||
switch {
|
||||
case bytes.Equal(t.Key(), []byte("e")):
|
||||
evId := make([]byte, sha256.Size)
|
||||
if _, err = hex.DecBytes(evId, t.Value()); chk.E(err) {
|
||||
continue
|
||||
}
|
||||
res, err = storage.QueryEvents(c, &filter.T{IDs: tag.New(evId)})
|
||||
if err != nil {
|
||||
err = huma.Error500InternalServerError(err.Error())
|
||||
return
|
||||
}
|
||||
for i := range res {
|
||||
if res[i].Kind.Equal(kind.Deletion) {
|
||||
err = huma.Error409Conflict("not processing or storing delete event containing delete event references")
|
||||
}
|
||||
if !bytes.Equal(res[i].PubKey, ev.PubKey) {
|
||||
err = huma.Error409Conflict("cannot delete other users' events (delete by e tag)")
|
||||
return
|
||||
}
|
||||
}
|
||||
case bytes.Equal(t.Key(), []byte("a")):
|
||||
split := bytes.Split(t.Value(), []byte{':'})
|
||||
if len(split) != 3 {
|
||||
continue
|
||||
}
|
||||
var pk []byte
|
||||
if pk, err = hex.DecAppend(nil, split[1]); chk.E(err) {
|
||||
err = huma.Error400BadRequest(fmt.Sprintf("delete event a tag pubkey value invalid: %s",
|
||||
t.Value()))
|
||||
return
|
||||
}
|
||||
kin := ints.New(uint16(0))
|
||||
if _, err = kin.Unmarshal(split[0]); chk.E(err) {
|
||||
err = huma.Error400BadRequest(fmt.Sprintf("delete event a tag kind value invalid: %s",
|
||||
t.Value()))
|
||||
return
|
||||
}
|
||||
kk := kind.New(kin.Uint16())
|
||||
if kk.Equal(kind.Deletion) {
|
||||
err = huma.Error403Forbidden("delete event kind may not be deleted")
|
||||
return
|
||||
}
|
||||
if !kk.IsParameterizedReplaceable() {
|
||||
err = huma.Error403Forbidden("delete tags with a tags containing non-parameterized-replaceable events cannot be processed")
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(pk, ev.PubKey) {
|
||||
log.I.S(pk, ev.PubKey, ev)
|
||||
err = huma.Error403Forbidden("cannot delete other users' events (delete by a tag)")
|
||||
return
|
||||
}
|
||||
f := filter.New()
|
||||
f.Kinds.K = []*kind.T{kk}
|
||||
f.Authors.Append(pk)
|
||||
f.Tags.AppendTags(tag.New([]byte{'#', 'd'}, split[2]))
|
||||
res, err = storage.QueryEvents(c, f)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), ERR)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(res) < 1 {
|
||||
continue
|
||||
}
|
||||
var resTmp []*event.T
|
||||
for _, v := range res {
|
||||
if ev.CreatedAt.U64() >= v.CreatedAt.U64() {
|
||||
resTmp = append(resTmp, v)
|
||||
}
|
||||
}
|
||||
res = resTmp
|
||||
for _, target := range res {
|
||||
if target.Kind.K == kind.Deletion.K {
|
||||
err = huma.Error403Forbidden(fmt.Sprintf(
|
||||
"cannot delete delete event %s", ev.ID))
|
||||
return
|
||||
}
|
||||
if target.CreatedAt.Int() > ev.CreatedAt.Int() {
|
||||
// todo: shouldn't this be an error?
|
||||
log.I.F("not deleting\n%d%\nbecause delete event is older\n%d",
|
||||
target.CreatedAt.Int(), ev.CreatedAt.Int())
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(target.PubKey, ev.PubKey) {
|
||||
err = huma.Error403Forbidden("only author can delete event")
|
||||
return
|
||||
}
|
||||
if advancedDeleter != nil {
|
||||
advancedDeleter.BeforeDelete(c, t.Value(), ev.PubKey)
|
||||
}
|
||||
if err = sto.DeleteEvent(c, target.EventID()); chk.T(err) {
|
||||
err = huma.Error500InternalServerError(err.Error())
|
||||
return
|
||||
}
|
||||
if advancedDeleter != nil {
|
||||
advancedDeleter.AfterDelete(t.Value(), ev.PubKey)
|
||||
}
|
||||
}
|
||||
res = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
var reason []byte
|
||||
ok, reason = s.addEvent(c, s.relay, ev, r, rr, pubkey)
|
||||
// return the response whether true or false and any reason if false
|
||||
if ok {
|
||||
} else {
|
||||
err = huma.Error500InternalServerError(string(reason))
|
||||
}
|
||||
if after != nil {
|
||||
// do this in the background and let the http response close
|
||||
go after()
|
||||
}
|
||||
wgh = &EventOutput{"event accepted"}
|
||||
return
|
||||
})
|
||||
}
|
||||
40
realy/huma.go
Normal file
40
realy/huma.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package realy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/danielgtaylor/huma/v2"
|
||||
"github.com/danielgtaylor/huma/v2/adapters/humago"
|
||||
)
|
||||
|
||||
// ExposeMiddleware adds the http.Request and http.ResponseWriter to the context
|
||||
// for the Operations handler.
|
||||
func ExposeMiddleware(ctx huma.Context, next func(huma.Context)) {
|
||||
// Unwrap the request and response objects.
|
||||
r, w := humago.Unwrap(ctx)
|
||||
ctx = huma.WithValue(ctx, "http-request", r)
|
||||
ctx = huma.WithValue(ctx, "http-response", w)
|
||||
next(ctx)
|
||||
}
|
||||
|
||||
func NewHuma(router *ServeMux, name, version, description string) (api huma.API) {
|
||||
apiConfig := huma.DefaultConfig(name, version)
|
||||
apiConfig.Info.Description = description
|
||||
// apiConfig.Security = []map[string][]string{{"auth": {}}}
|
||||
// apiConfig.Components.SecuritySchemes = map[string]*huma.SecurityScheme{"auth": {Type: "http", Scheme: "apiKey"}} // apiKey todo:
|
||||
apiConfig.DocsPath = "/api"
|
||||
api = humago.New(router, apiConfig)
|
||||
api.UseMiddleware(ExposeMiddleware)
|
||||
return
|
||||
}
|
||||
|
||||
func generateDescription(text string, scopes []string) string {
|
||||
if len(scopes) == 0 {
|
||||
return text
|
||||
}
|
||||
result := make([]string, 0)
|
||||
for _, value := range scopes {
|
||||
result = append(result, "`"+value+"`")
|
||||
}
|
||||
return text + "<br/><br/>**Scopes**<br/>" + strings.Join(result, ", ")
|
||||
}
|
||||
@@ -45,8 +45,8 @@ func (s *Server) handleRelayInfo(w http.ResponseWriter, r *http.Request) {
|
||||
sort.Sort(supportedNIPs)
|
||||
log.T.Ln("supported NIPs", supportedNIPs)
|
||||
info = &relayinfo.T{Name: s.relay.Name(),
|
||||
Description: "relay powered by the realy framework",
|
||||
Nips: supportedNIPs, Software: "https://realy.lol", Version: realy_lol.Version,
|
||||
Description: realy_lol.Description,
|
||||
Nips: supportedNIPs, Software: realy_lol.URL, Version: realy_lol.Version,
|
||||
Limitation: relayinfo.Limits{
|
||||
MaxLimit: s.maxLimit,
|
||||
AuthRequired: s.authRequired,
|
||||
|
||||
21
realy/serveMux.go
Normal file
21
realy/serveMux.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package realy
|
||||
|
||||
import "net/http"
|
||||
|
||||
type ServeMux struct {
|
||||
*http.ServeMux
|
||||
}
|
||||
|
||||
func NewServeMux() *ServeMux {
|
||||
return &ServeMux{http.NewServeMux()}
|
||||
}
|
||||
|
||||
func (c *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
if r.Method == http.MethodOptions {
|
||||
return
|
||||
}
|
||||
c.ServeMux.ServeHTTP(w, r)
|
||||
}
|
||||
@@ -11,9 +11,11 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/danielgtaylor/huma/v2"
|
||||
"github.com/fasthttp/websocket"
|
||||
"github.com/rs/cors"
|
||||
|
||||
realy_lol "realy.lol"
|
||||
"realy.lol/context"
|
||||
"realy.lol/realy/listeners"
|
||||
"realy.lol/realy/options"
|
||||
@@ -29,7 +31,7 @@ type Server struct {
|
||||
clientsMu sync.Mutex
|
||||
clients map[*websocket.Conn]struct{}
|
||||
Addr string
|
||||
mux *http.ServeMux
|
||||
mux *ServeMux
|
||||
httpServer *http.Server
|
||||
authRequired bool
|
||||
publicReadable bool
|
||||
@@ -37,6 +39,7 @@ type Server struct {
|
||||
admins []signer.I
|
||||
owners [][]byte
|
||||
listeners *listeners.T
|
||||
huma.API
|
||||
}
|
||||
|
||||
type ServerParams struct {
|
||||
@@ -67,12 +70,13 @@ func NewServer(sp *ServerParams, opts ...options.O) (*Server, error) {
|
||||
if err := sp.Rl.Init(); chk.T(err) {
|
||||
return nil, fmt.Errorf("realy init: %w", err)
|
||||
}
|
||||
serveMux := NewServeMux()
|
||||
srv := &Server{
|
||||
Ctx: sp.Ctx,
|
||||
Cancel: sp.Cancel,
|
||||
relay: sp.Rl,
|
||||
clients: make(map[*websocket.Conn]struct{}),
|
||||
mux: http.NewServeMux(),
|
||||
mux: serveMux,
|
||||
options: op,
|
||||
authRequired: authRequired,
|
||||
publicReadable: sp.PublicReadable,
|
||||
@@ -80,7 +84,9 @@ func NewServer(sp *ServerParams, opts ...options.O) (*Server, error) {
|
||||
admins: sp.Admins,
|
||||
owners: sp.Rl.Owners(),
|
||||
listeners: listeners.New(),
|
||||
API: NewHuma(serveMux, sp.Rl.Name(), realy_lol.Version, realy_lol.Description),
|
||||
}
|
||||
huma.AutoRegister(srv.API, NewEventPost(srv))
|
||||
if inj, ok := sp.Rl.(relay.Injector); ok {
|
||||
go func() {
|
||||
for ev := range inj.InjectEvents() {
|
||||
@@ -101,7 +107,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.handleWebsocket(w, r)
|
||||
return
|
||||
}
|
||||
s.HandleHTTP(w, r)
|
||||
log.I.S(r.URL)
|
||||
s.mux.ServeHTTP(w, r)
|
||||
// s.HandleHTTP(w, r)
|
||||
// s.mux.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
@@ -150,7 +158,7 @@ func (s *Server) Shutdown() {
|
||||
}
|
||||
|
||||
func (s *Server) Router() *http.ServeMux {
|
||||
return s.mux
|
||||
return s.mux.ServeMux
|
||||
}
|
||||
|
||||
func fprintf(w io.Writer, format string, a ...any) { _, _ = fmt.Fprintf(w, format, a...) }
|
||||
|
||||
@@ -91,7 +91,9 @@ func (t *T) Marshal(dst []byte) (b []byte) { return ints.New(t.U64()).Marshal(ds
|
||||
|
||||
func (t *T) Unmarshal(b []byte) (r []byte, err error) {
|
||||
n := ints.New(0)
|
||||
r, err = n.Unmarshal(b)
|
||||
if r, err = n.Unmarshal(b); chk.E(err) {
|
||||
return
|
||||
}
|
||||
*t = T{n.Int64()}
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user