Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
2.8 kB · 101 lines
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102package serviceauth
import ( "context" "encoding/json" "errors" "fmt" "log/slog" "net/http" "path" "strings"
"github.com/bluesky-social/indigo/atproto/auth" "github.com/bluesky-social/indigo/atproto/identity" "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/log" xrpcerr "tangled.org/core/xrpc/errors")
type contextKey string
const ActorDid contextKey = "ActorDid"
func DidWeb(hostname string) syntax.DID { return syntax.DID("did:web:" + strings.ReplaceAll(hostname, ":", "%3A"))}
type ServiceRkey string
func (r ServiceRkey) String() string { return string(r) }
func RkeyForService(hostname string) (ServiceRkey, error) { const maxRkeyLength = 512 switch rkey, stray := ServiceRkey(DidWeb(hostname)), strings.IndexAny(hostname, ":/%"); { case hostname == "": return "", errors.New("a service record key comes from a hostname, and this one is empty") case stray >= 0: return "", fmt.Errorf("a service record key comes from the bare host, and %q contains %q", hostname, hostname[stray]) case len(rkey) > maxRkeyLength: return "", fmt.Errorf("a record key from %q is %d bytes, over the %d a repository allows", hostname, len(rkey), maxRkeyLength) default: return rkey, nil }}
type ServiceAuth struct { logger *slog.Logger dir identity.Directory audienceDid string}
func NewServiceAuth(logger *slog.Logger, dir identity.Directory, audienceDid string) *ServiceAuth { return &ServiceAuth{ logger: log.SubLogger(logger, "serviceauth"), dir: dir, audienceDid: audienceDid, }}
func (sa *ServiceAuth) VerifyServiceAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("Authorization") token = strings.TrimPrefix(token, "Bearer ")
lxm, err := syntax.ParseNSID(path.Base(r.URL.Path)) if err != nil { sa.logger.Error("couldn't derive lexicon method from request path", "path", r.URL.Path, "err", err) writeError(w, xrpcerr.AuthError(err), http.StatusForbidden) return }
s := auth.ServiceAuthValidator{ Audience: sa.audienceDid, Dir: sa.dir, }
did, err := s.Validate(r.Context(), token, &lxm) if err != nil { sa.logger.Error("signature verification failed", "err", err) writeError(w, xrpcerr.AuthError(err), http.StatusForbidden) return }
sa.logger.Debug("valid signature", "did", did)
r = r.WithContext( context.WithValue(r.Context(), ActorDid, did), )
next.ServeHTTP(w, r) })}
// 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 xrpcerr.XrpcError, status int) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(e)}