forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
1package serviceauth 2 3import ( 4 "context" 5 "encoding/json" 6 "log/slog" 7 "net/http" 8 "strings" 9 10 "github.com/bluesky-social/indigo/atproto/auth" 11 "tangled.org/core/idresolver" 12 "tangled.org/core/log" 13 xrpcerr "tangled.org/core/xrpc/errors" 14) 15 16const ActorDid string = "ActorDid" 17 18type ServiceAuth struct { 19 logger *slog.Logger 20 resolver *idresolver.Resolver 21 audienceDid string 22} 23 24func NewServiceAuth(logger *slog.Logger, resolver *idresolver.Resolver, audienceDid string) *ServiceAuth { 25 return &ServiceAuth{ 26 logger: log.SubLogger(logger, "serviceauth"), 27 resolver: resolver, 28 audienceDid: audienceDid, 29 } 30} 31 32func (sa *ServiceAuth) VerifyServiceAuth(next http.Handler) http.Handler { 33 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 34 token := r.Header.Get("Authorization") 35 token = strings.TrimPrefix(token, "Bearer ") 36 37 s := auth.ServiceAuthValidator{ 38 Audience: sa.audienceDid, 39 Dir: sa.resolver.Directory(), 40 } 41 42 did, err := s.Validate(r.Context(), token, nil) 43 if err != nil { 44 sa.logger.Error("signature verification failed", "err", err) 45 writeError(w, xrpcerr.AuthError(err), http.StatusForbidden) 46 return 47 } 48 49 sa.logger.Debug("valid signature", ActorDid, did) 50 51 r = r.WithContext( 52 context.WithValue(r.Context(), ActorDid, did), 53 ) 54 55 next.ServeHTTP(w, r) 56 }) 57} 58 59// this is slightly different from http_util::write_error to follow the spec: 60// 61// the json object returned must include an "error" and a "message" 62func writeError(w http.ResponseWriter, e xrpcerr.XrpcError, status int) { 63 w.Header().Set("Content-Type", "application/json") 64 w.WriteHeader(status) 65 json.NewEncoder(w).Encode(e) 66}