implement auth and a simple admin-follows whitelist
Some checks failed
Go / build (push) Has been cancelled
Some checks failed
Go / build (push) Has been cancelled
This commit is contained in:
@@ -1 +1,50 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoders.orly/envelopes/authenvelope"
|
||||
"encoders.orly/envelopes/okenvelope"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"protocol.orly/auth"
|
||||
)
|
||||
|
||||
func (l *Listener) HandleAuth(b []byte) (err error) {
|
||||
var rem []byte
|
||||
env := authenvelope.NewResponse()
|
||||
if rem, err = env.Unmarshal(b); chk.E(err) {
|
||||
return
|
||||
}
|
||||
if len(rem) > 0 {
|
||||
log.I.F("extra '%s'", rem)
|
||||
}
|
||||
var valid bool
|
||||
if valid, err = auth.Validate(
|
||||
env.Event, l.challenge.Load(),
|
||||
l.ServiceURL(l.req),
|
||||
); err != nil {
|
||||
e := err.Error()
|
||||
if err = Ok.Error(l, env, e); chk.E(err) {
|
||||
return
|
||||
}
|
||||
return
|
||||
} else if !valid {
|
||||
if err = Ok.Error(
|
||||
l, env, "auth response event is invalid",
|
||||
); chk.E(err) {
|
||||
return
|
||||
}
|
||||
return
|
||||
} else {
|
||||
if err = okenvelope.NewFrom(
|
||||
env.Event.ID, true,
|
||||
).Write(l); chk.E(err) {
|
||||
return
|
||||
}
|
||||
log.D.F(
|
||||
"%s authed to pubkey,%0x", l.remote,
|
||||
env.Event.Pubkey,
|
||||
)
|
||||
l.authedPubkey.Store(env.Event.Pubkey)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@ import (
|
||||
"strings"
|
||||
|
||||
acl "acl.orly"
|
||||
"encoders.orly/envelopes/authenvelope"
|
||||
"encoders.orly/envelopes/eventenvelope"
|
||||
"encoders.orly/envelopes/okenvelope"
|
||||
"encoders.orly/kind"
|
||||
"encoders.orly/reason"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
utils "utils.orly"
|
||||
@@ -53,6 +56,51 @@ func (l *Listener) HandleEvent(msg []byte) (err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
// // send a challenge to the client to auth if an ACL is active and not authed
|
||||
// if acl.Registry.Active.Load() != "none" && l.authedPubkey.Load() == nil {
|
||||
// log.D.F("sending challenge to %s", l.remote)
|
||||
// if err = authenvelope.NewChallengeWith(l.challenge.Load()).
|
||||
// Write(l); chk.E(err) {
|
||||
// // return
|
||||
// }
|
||||
// // ACL is enabled so return and wait for auth
|
||||
// // return
|
||||
// }
|
||||
// check permissions of user
|
||||
accessLevel := acl.Registry.GetAccessLevel(l.authedPubkey.Load())
|
||||
switch accessLevel {
|
||||
case "none":
|
||||
log.D.F("handle event: sending CLOSED to %s", l.remote)
|
||||
if err = okenvelope.NewFrom(
|
||||
env.Id(), false,
|
||||
reason.AuthRequired.F("auth required for write access"),
|
||||
).Write(l); chk.E(err) {
|
||||
// return
|
||||
}
|
||||
log.D.F("handle event: sending challenge to %s", l.remote)
|
||||
if err = authenvelope.NewChallengeWith(l.challenge.Load()).
|
||||
Write(l); chk.E(err) {
|
||||
return
|
||||
}
|
||||
return
|
||||
case "read":
|
||||
log.D.F("handle event: sending CLOSED to %s", l.remote)
|
||||
if err = okenvelope.NewFrom(
|
||||
env.Id(), false,
|
||||
reason.AuthRequired.F("auth required for write access"),
|
||||
).Write(l); chk.E(err) {
|
||||
// return
|
||||
}
|
||||
log.D.F("handle event: sending challenge to %s", l.remote)
|
||||
if err = authenvelope.NewChallengeWith(l.challenge.Load()).
|
||||
Write(l); chk.E(err) {
|
||||
// return
|
||||
}
|
||||
return
|
||||
default:
|
||||
// user has write access or better, continue
|
||||
log.D.F("user has %s access", accessLevel)
|
||||
}
|
||||
// if the event is a delete, process the delete
|
||||
if env.E.Kind == kind.EventDeletion.K {
|
||||
l.HandleDelete(env)
|
||||
|
||||
@@ -38,6 +38,7 @@ func (l *Listener) HandleMessage(msg []byte, remote string) {
|
||||
err = l.HandleClose(rem)
|
||||
case authenvelope.L:
|
||||
log.D.F("authenvelope: %s", rem)
|
||||
err = l.HandleAuth(rem)
|
||||
default:
|
||||
err = errorf.E("unknown envelope type %s\n%s", t, rem)
|
||||
}
|
||||
|
||||
@@ -44,6 +44,23 @@ func (s *Server) HandleRelayInfo(w http.ResponseWriter, r *http.Request) {
|
||||
// relayinfo.ProtectedEvents,
|
||||
// relayinfo.RelayListMetadata,
|
||||
)
|
||||
if s.Config.ACLMode != "none" {
|
||||
supportedNIPs = relayinfo.GetList(
|
||||
relayinfo.BasicProtocol,
|
||||
relayinfo.Authentication,
|
||||
// relayinfo.EncryptedDirectMessage,
|
||||
// relayinfo.EventDeletion,
|
||||
relayinfo.RelayInformationDocument,
|
||||
// relayinfo.GenericTagQueries,
|
||||
// relayinfo.NostrMarketplace,
|
||||
// relayinfo.EventTreatment,
|
||||
// relayinfo.CommandResults,
|
||||
// relayinfo.ParameterizedReplaceableEvents,
|
||||
// relayinfo.ExpirationTimestamp,
|
||||
// relayinfo.ProtectedEvents,
|
||||
// relayinfo.RelayListMetadata,
|
||||
)
|
||||
}
|
||||
sort.Sort(supportedNIPs)
|
||||
log.T.Ln("supported NIPs", supportedNIPs)
|
||||
info = &relayinfo.T{
|
||||
@@ -52,9 +69,9 @@ func (s *Server) HandleRelayInfo(w http.ResponseWriter, r *http.Request) {
|
||||
Nips: supportedNIPs,
|
||||
Software: version.URL,
|
||||
Version: version.V,
|
||||
Limitation: relayinfo.Limits{
|
||||
// AuthRequired: s.C.AuthRequired,
|
||||
// RestrictedWrites: s.C.AuthRequired,
|
||||
Limitation: relayinfo.Limits{
|
||||
AuthRequired: s.Config.ACLMode != "none",
|
||||
RestrictedWrites: s.Config.ACLMode != "none",
|
||||
},
|
||||
Icon: "https://cdn.satellite.earth/ac9778868fbf23b63c47c769a74e163377e6ea94d3f0f31711931663d035c4f6.png",
|
||||
}
|
||||
|
||||
@@ -3,12 +3,16 @@ package app
|
||||
import (
|
||||
"errors"
|
||||
|
||||
acl "acl.orly"
|
||||
"encoders.orly/envelopes/authenvelope"
|
||||
"encoders.orly/envelopes/closedenvelope"
|
||||
"encoders.orly/envelopes/eoseenvelope"
|
||||
"encoders.orly/envelopes/eventenvelope"
|
||||
"encoders.orly/envelopes/okenvelope"
|
||||
"encoders.orly/envelopes/reqenvelope"
|
||||
"encoders.orly/event"
|
||||
"encoders.orly/filter"
|
||||
"encoders.orly/reason"
|
||||
"encoders.orly/tag"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"lol.mleku.dev/chk"
|
||||
@@ -28,6 +32,50 @@ func (l *Listener) HandleReq(msg []byte) (
|
||||
if len(rem) > 0 {
|
||||
log.I.F("extra '%s'", rem)
|
||||
}
|
||||
// // send a challenge to the client to auth if an ACL is active and not authed
|
||||
// if acl.Registry.Active.Load() != "none" && l.authedPubkey.Load() == nil {
|
||||
// log.D.F("sending challenge to %s", l.remote)
|
||||
// if err = authenvelope.NewChallengeWith(l.challenge.Load()).
|
||||
// Write(l); chk.E(err) {
|
||||
// // return
|
||||
// }
|
||||
// log.D.F("sending CLOSED to %s", l.remote)
|
||||
// if err = closedenvelope.NewFrom(
|
||||
// env.Subscription, reason.AuthRequired.F("auth required for access"),
|
||||
// ).Write(l); chk.E(err) {
|
||||
// return
|
||||
// }
|
||||
// // ACL is enabled so return and wait for auth
|
||||
// // return
|
||||
// }
|
||||
// send a challenge to the client to auth if an ACL is active
|
||||
if acl.Registry.Active.Load() != "none" {
|
||||
// log.D.F("sending CLOSED to %s", l.remote)
|
||||
// if err = closedenvelope.NewFrom(
|
||||
// env.Subscription, reason.AuthRequired.F("auth required for access"),
|
||||
// ).Write(l); chk.E(err) {
|
||||
// // return
|
||||
// }
|
||||
if err = authenvelope.NewChallengeWith(l.challenge.Load()).
|
||||
Write(l); chk.E(err) {
|
||||
// return
|
||||
}
|
||||
}
|
||||
// check permissions of user
|
||||
accessLevel := acl.Registry.GetAccessLevel(l.authedPubkey.Load())
|
||||
switch accessLevel {
|
||||
case "none":
|
||||
if err = okenvelope.NewFrom(
|
||||
env.Subscription, false,
|
||||
reason.AuthRequired.F("user not authed or has no read access"),
|
||||
).Write(l); chk.E(err) {
|
||||
return
|
||||
}
|
||||
return
|
||||
default:
|
||||
// user has read access or better, continue
|
||||
log.D.F("user has %s access", accessLevel)
|
||||
}
|
||||
var events event.S
|
||||
for _, f := range *env.Filters {
|
||||
if pointers.Present(f.Limit) {
|
||||
|
||||
@@ -2,10 +2,12 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"encoders.orly/hex"
|
||||
"github.com/coder/websocket"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
@@ -63,7 +65,11 @@ whitelist:
|
||||
Server: s,
|
||||
conn: conn,
|
||||
remote: remote,
|
||||
req: r,
|
||||
}
|
||||
chal := make([]byte, 32)
|
||||
rand.Read(chal)
|
||||
listener.challenge.Store([]byte(hex.Enc(chal)))
|
||||
ticker := time.NewTicker(DefaultPingWait)
|
||||
go s.Pinger(ctx, conn, ticker, cancel)
|
||||
defer func() {
|
||||
|
||||
@@ -2,16 +2,21 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"lol.mleku.dev/chk"
|
||||
"utils.orly/atomic"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
*Server
|
||||
conn *websocket.Conn
|
||||
ctx context.Context
|
||||
remote string
|
||||
conn *websocket.Conn
|
||||
ctx context.Context
|
||||
remote string
|
||||
req *http.Request
|
||||
challenge atomic.Bytes
|
||||
authedPubkey atomic.Bytes
|
||||
}
|
||||
|
||||
func (l *Listener) Write(p []byte) (n int, err error) {
|
||||
|
||||
22
app/ok.go
22
app/ok.go
@@ -10,7 +10,7 @@ import (
|
||||
// parameters to generate formatted messages and return errors if any issues
|
||||
// occur during processing.
|
||||
type OK func(
|
||||
l *Listener, env *eventenvelope.Submission, format string, params ...any,
|
||||
l *Listener, env eventenvelope.I, format string, params ...any,
|
||||
) (err error)
|
||||
|
||||
// OKs provides a collection of handler functions for managing different types
|
||||
@@ -36,7 +36,7 @@ type OKs struct {
|
||||
// inputs.
|
||||
var Ok = OKs{
|
||||
Ok: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
l *Listener, env eventenvelope.I, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
@@ -44,7 +44,7 @@ var Ok = OKs{
|
||||
).Write(l)
|
||||
},
|
||||
AuthRequired: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
l *Listener, env eventenvelope.I, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
@@ -52,7 +52,7 @@ var Ok = OKs{
|
||||
).Write(l)
|
||||
},
|
||||
PoW: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
l *Listener, env eventenvelope.I, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
@@ -60,7 +60,7 @@ var Ok = OKs{
|
||||
).Write(l)
|
||||
},
|
||||
Duplicate: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
l *Listener, env eventenvelope.I, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
@@ -68,7 +68,7 @@ var Ok = OKs{
|
||||
).Write(l)
|
||||
},
|
||||
Blocked: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
l *Listener, env eventenvelope.I, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
@@ -76,7 +76,7 @@ var Ok = OKs{
|
||||
).Write(l)
|
||||
},
|
||||
RateLimited: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
l *Listener, env eventenvelope.I, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
@@ -84,7 +84,7 @@ var Ok = OKs{
|
||||
).Write(l)
|
||||
},
|
||||
Invalid: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
l *Listener, env eventenvelope.I, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
@@ -92,7 +92,7 @@ var Ok = OKs{
|
||||
).Write(l)
|
||||
},
|
||||
Error: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
l *Listener, env eventenvelope.I, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
@@ -100,7 +100,7 @@ var Ok = OKs{
|
||||
).Write(l)
|
||||
},
|
||||
Unsupported: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
l *Listener, env eventenvelope.I, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
@@ -108,7 +108,7 @@ var Ok = OKs{
|
||||
).Write(l)
|
||||
},
|
||||
Restricted: func(
|
||||
l *Listener, env *eventenvelope.Submission, format string,
|
||||
l *Listener, env eventenvelope.I, format string,
|
||||
params ...any,
|
||||
) (err error) {
|
||||
return okenvelope.NewFrom(
|
||||
|
||||
@@ -4,8 +4,11 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"database.orly"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/app/config"
|
||||
"protocol.orly/publish"
|
||||
@@ -39,3 +42,33 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
func (s *Server) ServiceURL(req *http.Request) (st string) {
|
||||
host := req.Header.Get("X-Forwarded-Host")
|
||||
if host == "" {
|
||||
host = req.Host
|
||||
}
|
||||
proto := req.Header.Get("X-Forwarded-Proto")
|
||||
if proto == "" {
|
||||
if host == "localhost" {
|
||||
proto = "ws"
|
||||
} else if strings.Contains(host, ":") {
|
||||
// has a port number
|
||||
proto = "ws"
|
||||
} else if _, err := strconv.Atoi(
|
||||
strings.ReplaceAll(
|
||||
host, ".",
|
||||
"",
|
||||
),
|
||||
); chk.E(err) {
|
||||
// it's a naked IP
|
||||
proto = "ws"
|
||||
} else {
|
||||
proto = "wss"
|
||||
}
|
||||
} else if proto == "https" {
|
||||
proto = "wss"
|
||||
} else if proto == "http" {
|
||||
proto = "ws"
|
||||
}
|
||||
return proto + "://" + host
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user