forked from tangled.org/core
this repo has no description

knotserver/xrpc: use new top-level xrpc packages

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.sh>

anirudh.fi c61ac86d cf8fa325

verified
Changed files
+35 -131
knotserver
+11 -7
knotserver/handler.go
···
tlog "tangled.sh/tangled.sh/core/log"
"tangled.sh/tangled.sh/core/notifier"
"tangled.sh/tangled.sh/core/rbac"
+
"tangled.sh/tangled.sh/core/xrpc/serviceauth"
)
type Handle struct {
···
func (h *Handle) XrpcRouter() http.Handler {
logger := tlog.New("knots")
+
serviceAuth := serviceauth.NewServiceAuth(h.l, h.resolver, h.c.Server.Did().String())
+
xrpc := &xrpc.Xrpc{
-
Config: h.c,
-
Db: h.db,
-
Ingester: h.jc,
-
Enforcer: h.e,
-
Logger: logger,
-
Notifier: h.n,
-
Resolver: h.resolver,
+
Config: h.c,
+
Db: h.db,
+
Ingester: h.jc,
+
Enforcer: h.e,
+
Logger: logger,
+
Notifier: h.n,
+
Resolver: h.resolver,
+
ServiceAuth: serviceAuth,
}
return xrpc.Router()
}
+12 -114
knotserver/xrpc/router.go
···
package xrpc
import (
-
"context"
"encoding/json"
-
"fmt"
"log/slog"
"net/http"
-
"strings"
"tangled.sh/tangled.sh/core/api/tangled"
"tangled.sh/tangled.sh/core/idresolver"
···
"tangled.sh/tangled.sh/core/knotserver/db"
"tangled.sh/tangled.sh/core/notifier"
"tangled.sh/tangled.sh/core/rbac"
+
xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
+
"tangled.sh/tangled.sh/core/xrpc/serviceauth"
-
"github.com/bluesky-social/indigo/atproto/auth"
"github.com/go-chi/chi/v5"
)
type Xrpc struct {
-
Config *config.Config
-
Db *db.DB
-
Ingester *jetstream.JetstreamClient
-
Enforcer *rbac.Enforcer
-
Logger *slog.Logger
-
Notifier *notifier.Notifier
-
Resolver *idresolver.Resolver
+
Config *config.Config
+
Db *db.DB
+
Ingester *jetstream.JetstreamClient
+
Enforcer *rbac.Enforcer
+
Logger *slog.Logger
+
Notifier *notifier.Notifier
+
Resolver *idresolver.Resolver
+
ServiceAuth *serviceauth.ServiceAuth
}
func (x *Xrpc) Router() http.Handler {
r := chi.NewRouter()
-
r.With(x.VerifyServiceAuth).Post("/"+tangled.RepoSetDefaultBranchNSID, x.SetDefaultBranch)
+
r.With(x.ServiceAuth.VerifyServiceAuth).Post("/"+tangled.RepoSetDefaultBranchNSID, x.SetDefaultBranch)
return r
}
-
func (x *Xrpc) VerifyServiceAuth(next http.Handler) http.Handler {
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-
l := x.Logger.With("url", r.URL)
-
-
token := r.Header.Get("Authorization")
-
token = strings.TrimPrefix(token, "Bearer ")
-
-
s := auth.ServiceAuthValidator{
-
Audience: x.Config.Server.Did().String(),
-
Dir: x.Resolver.Directory(),
-
}
-
-
did, err := s.Validate(r.Context(), token, nil)
-
if err != nil {
-
l.Error("signature verification failed", "err", err)
-
writeError(w, AuthError(err), http.StatusForbidden)
-
return
-
}
-
-
r = r.WithContext(
-
context.WithValue(r.Context(), ActorDid, did),
-
)
-
-
next.ServeHTTP(w, r)
-
})
-
}
-
-
type XrpcError struct {
-
Tag string `json:"error"`
-
Message string `json:"message"`
-
}
-
-
func NewXrpcError(opts ...ErrOpt) XrpcError {
-
x := XrpcError{}
-
for _, o := range opts {
-
o(&x)
-
}
-
-
return x
-
}
-
-
type ErrOpt = func(xerr *XrpcError)
-
-
func WithTag(tag string) ErrOpt {
-
return func(xerr *XrpcError) {
-
xerr.Tag = tag
-
}
-
}
-
-
func WithMessage[S ~string](s S) ErrOpt {
-
return func(xerr *XrpcError) {
-
xerr.Message = string(s)
-
}
-
}
-
-
func WithError(e error) ErrOpt {
-
return func(xerr *XrpcError) {
-
xerr.Message = e.Error()
-
}
-
}
-
-
var MissingActorDidError = NewXrpcError(
-
WithTag("MissingActorDid"),
-
WithMessage("actor DID not supplied"),
-
)
-
-
var AuthError = func(err error) XrpcError {
-
return NewXrpcError(
-
WithTag("Auth"),
-
WithError(fmt.Errorf("signature verification failed: %w", err)),
-
)
-
}
-
-
var InvalidRepoError = func(r string) XrpcError {
-
return NewXrpcError(
-
WithTag("InvalidRepo"),
-
WithError(fmt.Errorf("supplied at-uri is not a repo: %s", r)),
-
)
-
}
-
-
var AccessControlError = func(d string) XrpcError {
-
return NewXrpcError(
-
WithTag("AccessControl"),
-
WithError(fmt.Errorf("DID does not have sufficent access permissions for this operation: %s", d)),
-
)
-
}
-
-
var GitError = func(e error) XrpcError {
-
return NewXrpcError(
-
WithTag("Git"),
-
WithError(fmt.Errorf("git error: %w", e)),
-
)
-
}
-
-
func GenericError(err error) XrpcError {
-
return NewXrpcError(
-
WithTag("Generic"),
-
WithError(err),
-
)
-
}
-
// this is slightly different from http_util::write_error to follow the spec:
//
// the json object returned must include an "error" and a "message"
-
func writeError(w http.ResponseWriter, e XrpcError, status int) {
+
func writeError(w http.ResponseWriter, e xrpcerr.XrpcError, status int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(e)
+12 -10
knotserver/xrpc/set_default_branch.go
···
"tangled.sh/tangled.sh/core/api/tangled"
"tangled.sh/tangled.sh/core/knotserver/git"
"tangled.sh/tangled.sh/core/rbac"
+
+
xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
)
const ActorDid string = "ActorDid"
func (x *Xrpc) SetDefaultBranch(w http.ResponseWriter, r *http.Request) {
l := x.Logger
-
fail := func(e XrpcError) {
+
fail := func(e xrpcerr.XrpcError) {
l.Error("failed", "kind", e.Tag, "error", e.Message)
writeError(w, e, http.StatusBadRequest)
}
actorDid, ok := r.Context().Value(ActorDid).(syntax.DID)
if !ok {
-
fail(MissingActorDidError)
+
fail(xrpcerr.MissingActorDidError)
return
}
var data tangled.RepoSetDefaultBranch_Input
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
-
fail(GenericError(err))
+
fail(xrpcerr.GenericError(err))
return
}
// unfortunately we have to resolve repo-at here
repoAt, err := syntax.ParseATURI(data.Repo)
if err != nil {
-
fail(InvalidRepoError(data.Repo))
+
fail(xrpcerr.InvalidRepoError(data.Repo))
return
}
// resolve this aturi to extract the repo record
ident, err := x.Resolver.ResolveIdent(r.Context(), repoAt.Authority().String())
if err != nil || ident.Handle.IsInvalidHandle() {
-
fail(GenericError(fmt.Errorf("failed to resolve handle: %w", err)))
+
fail(xrpcerr.GenericError(fmt.Errorf("failed to resolve handle: %w", err)))
return
}
xrpcc := xrpc.Client{Host: ident.PDSEndpoint()}
resp, err := comatproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String())
if err != nil {
-
fail(GenericError(err))
+
fail(xrpcerr.GenericError(err))
return
}
repo := resp.Value.Val.(*tangled.Repo)
didPath, err := securejoin.SecureJoin(actorDid.String(), repo.Name)
if err != nil {
-
fail(GenericError(err))
+
fail(xrpcerr.GenericError(err))
return
}
if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil {
l.Error("insufficent permissions", "did", actorDid.String())
-
writeError(w, AccessControlError(actorDid.String()), http.StatusUnauthorized)
+
writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized)
return
}
path, _ := securejoin.SecureJoin(x.Config.Repo.ScanPath, didPath)
gr, err := git.PlainOpen(path)
if err != nil {
-
fail(InvalidRepoError(data.Repo))
+
fail(xrpcerr.GenericError(err))
return
}
err = gr.SetDefaultBranch(data.DefaultBranch)
if err != nil {
l.Error("setting default branch", "error", err.Error())
-
writeError(w, GitError(err), http.StatusInternalServerError)
+
writeError(w, xrpcerr.GitError(err), http.StatusInternalServerError)
return
}