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}