A demo of a Bluesky feed generator in Go

Fix the auth check

+97 -2
+88
auth.go
··· 1 + package feed 2 + 3 + import ( 4 + "fmt" 5 + "net/http" 6 + "strings" 7 + 8 + "github.com/bluesky-social/indigo/atproto/crypto" 9 + "github.com/bluesky-social/indigo/atproto/identity" 10 + "github.com/bluesky-social/indigo/atproto/syntax" 11 + "github.com/golang-jwt/jwt/v5" 12 + ) 13 + 14 + const ( 15 + ES256K = "ES256K" 16 + ES256 = "ES256" 17 + ) 18 + 19 + func init() { 20 + ES256K := AtProtoSigningMethod{alg: "ES256K"} 21 + jwt.RegisterSigningMethod(ES256K.Alg(), func() jwt.SigningMethod { 22 + return &ES256K 23 + }) 24 + 25 + ES256 := AtProtoSigningMethod{alg: "ES256"} 26 + jwt.RegisterSigningMethod(ES256.Alg(), func() jwt.SigningMethod { 27 + return &ES256 28 + }) 29 + 30 + } 31 + 32 + type AtProtoSigningMethod struct { 33 + alg string 34 + } 35 + 36 + func (m *AtProtoSigningMethod) Alg() string { 37 + return m.alg 38 + } 39 + 40 + func (m *AtProtoSigningMethod) Verify(signingString string, signature []byte, key interface{}) error { 41 + err := key.(crypto.PublicKey).HashAndVerifyLenient([]byte(signingString), signature) 42 + return err 43 + } 44 + 45 + func (m *AtProtoSigningMethod) Sign(signingString string, key interface{}) ([]byte, error) { 46 + return nil, fmt.Errorf("unimplemented") 47 + } 48 + 49 + func getRequestUserDID(r *http.Request) (string, error) { 50 + headerValues := r.Header["Authorization"] 51 + 52 + if len(headerValues) != 1 { 53 + return "", fmt.Errorf("missing authorization header") 54 + } 55 + token := strings.TrimSpace(strings.Replace(headerValues[0], "Bearer ", "", 1)) 56 + 57 + keyfunc := func(token *jwt.Token) (any, error) { 58 + did := syntax.DID(token.Claims.(jwt.MapClaims)["iss"].(string)) 59 + identity, err := identity.DefaultDirectory().LookupDID(r.Context(), did) 60 + if err != nil { 61 + return nil, fmt.Errorf("unable to resolve did %s: %s", did, err) 62 + } 63 + key, err := identity.PublicKey() 64 + if err != nil { 65 + return nil, fmt.Errorf("signing key not found for did %s: %s", did, err) 66 + } 67 + return key, nil 68 + } 69 + 70 + validMethods := jwt.WithValidMethods([]string{ES256, ES256K}) 71 + 72 + parsedToken, err := jwt.ParseWithClaims(token, jwt.MapClaims{}, keyfunc, validMethods) 73 + if err != nil { 74 + return "", fmt.Errorf("invalid token: %s", err) 75 + } 76 + 77 + claims, ok := parsedToken.Claims.(jwt.MapClaims) 78 + if !ok { 79 + return "", fmt.Errorf("token contained no claims") 80 + } 81 + 82 + issVal, ok := claims["iss"].(string) 83 + if !ok { 84 + return "", fmt.Errorf("iss claim missing") 85 + } 86 + 87 + return string(syntax.DID(issVal)), nil 88 + }
database.db

This is a binary file and will not be displayed.

+9 -2
handlers.go
··· 12 12 13 13 const ( 14 14 defaultLimit = 50 15 - ES256K = "ES256K" 16 - ES256 = "ES256" 17 15 ) 18 16 19 17 // FeedSkeletonReponse describes a response that will contain a skeleton feed ··· 31 29 // HandleGetFeedSkeleton is the handler that will build up and return a feed response 32 30 func (s *Server) HandleGetFeedSkeleton(w http.ResponseWriter, r *http.Request) { 33 31 slog.Debug("got request for feed skeleton", "host", r.RemoteAddr) 32 + 33 + // if you need to get a feed based on the user making the request you can use this to get the callers DID. 34 + // It's also a good idea to have this here incase you're getting spammed by non bluesky users - looking at you bots! 35 + _, err := getRequestUserDID(r) 36 + if err != nil { 37 + slog.Error("validate user auth", "error", err) 38 + http.Error(w, "validate auth", http.StatusUnauthorized) 39 + return 40 + } 34 41 35 42 params := r.URL.Query() 36 43