forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
Monorepo for Tangled
fork
Configure Feed
Select the types of activity you want to include in your feed.
1package xrpc
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "log/slog"
8 "net/http"
9 "strings"
10
11 "tangled.sh/tangled.sh/core/api/tangled"
12 "tangled.sh/tangled.sh/core/idresolver"
13 "tangled.sh/tangled.sh/core/jetstream"
14 "tangled.sh/tangled.sh/core/knotserver/config"
15 "tangled.sh/tangled.sh/core/knotserver/db"
16 "tangled.sh/tangled.sh/core/notifier"
17 "tangled.sh/tangled.sh/core/rbac"
18
19 "github.com/bluesky-social/indigo/atproto/auth"
20 "github.com/go-chi/chi/v5"
21)
22
23type Xrpc struct {
24 Config *config.Config
25 Db *db.DB
26 Ingester *jetstream.JetstreamClient
27 Enforcer *rbac.Enforcer
28 Logger *slog.Logger
29 Notifier *notifier.Notifier
30 Resolver *idresolver.Resolver
31}
32
33func (x *Xrpc) Router() http.Handler {
34 r := chi.NewRouter()
35
36 r.With(x.VerifyServiceAuth).Post("/"+tangled.RepoSetDefaultBranchNSID, x.SetDefaultBranch)
37
38 return r
39}
40
41func (x *Xrpc) VerifyServiceAuth(next http.Handler) http.Handler {
42 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
43 l := x.Logger.With("url", r.URL)
44
45 token := r.Header.Get("Authorization")
46 token = strings.TrimPrefix(token, "Bearer ")
47
48 s := auth.ServiceAuthValidator{
49 Audience: x.Config.Server.Did().String(),
50 Dir: x.Resolver.Directory(),
51 }
52
53 did, err := s.Validate(r.Context(), token, nil)
54 if err != nil {
55 l.Error("signature verification failed", "err", err)
56 writeError(w, AuthError(err), http.StatusForbidden)
57 return
58 }
59
60 r = r.WithContext(
61 context.WithValue(r.Context(), ActorDid, did),
62 )
63
64 next.ServeHTTP(w, r)
65 })
66}
67
68type XrpcError struct {
69 Tag string `json:"error"`
70 Message string `json:"message"`
71}
72
73func NewXrpcError(opts ...ErrOpt) XrpcError {
74 x := XrpcError{}
75 for _, o := range opts {
76 o(&x)
77 }
78
79 return x
80}
81
82type ErrOpt = func(xerr *XrpcError)
83
84func WithTag(tag string) ErrOpt {
85 return func(xerr *XrpcError) {
86 xerr.Tag = tag
87 }
88}
89
90func WithMessage[S ~string](s S) ErrOpt {
91 return func(xerr *XrpcError) {
92 xerr.Message = string(s)
93 }
94}
95
96func WithError(e error) ErrOpt {
97 return func(xerr *XrpcError) {
98 xerr.Message = e.Error()
99 }
100}
101
102var MissingActorDidError = NewXrpcError(
103 WithTag("MissingActorDid"),
104 WithMessage("actor DID not supplied"),
105)
106
107var AuthError = func(err error) XrpcError {
108 return NewXrpcError(
109 WithTag("Auth"),
110 WithError(fmt.Errorf("signature verification failed: %w", err)),
111 )
112}
113
114var InvalidRepoError = func(r string) XrpcError {
115 return NewXrpcError(
116 WithTag("InvalidRepo"),
117 WithError(fmt.Errorf("supplied at-uri is not a repo: %s", r)),
118 )
119}
120
121var AccessControlError = func(d string) XrpcError {
122 return NewXrpcError(
123 WithTag("AccessControl"),
124 WithError(fmt.Errorf("DID does not have sufficent access permissions for this operation: %s", d)),
125 )
126}
127
128var GitError = func(e error) XrpcError {
129 return NewXrpcError(
130 WithTag("Git"),
131 WithError(fmt.Errorf("git error: %w", e)),
132 )
133}
134
135func GenericError(err error) XrpcError {
136 return NewXrpcError(
137 WithTag("Generic"),
138 WithError(err),
139 )
140}
141
142// this is slightly different from http_util::write_error to follow the spec:
143//
144// the json object returned must include an "error" and a "message"
145func writeError(w http.ResponseWriter, e XrpcError, status int) {
146 w.Header().Set("Content-Type", "application/json")
147 w.WriteHeader(status)
148 json.NewEncoder(w).Encode(e)
149}