this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at d1b693326728d4e2652c2ba65ac15b1b4e1c2b9f 153 lines 4.0 kB view raw
1package main 2 3import ( 4 "fmt" 5 "log/slog" 6 "net/http" 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/bluesky-social/indigo/atproto/crypto" 12 "github.com/bluesky-social/indigo/atproto/identity" 13 "github.com/bluesky-social/indigo/atproto/syntax" 14 "github.com/golang-jwt/jwt/v5" 15 "github.com/willdot/bskyfeedgen/frontend" 16) 17 18// The contents of this file have been borrowed from here: https://github.com/orthanc/bluesky-go-feeds/blob/f719f113f1afc9080e50b4b1f5ca239aa3073c79/web/auth.go#L20-L46 19// It essentially allows the signing method that atproto uses for JWT to be used when verifying the JWT that they send in requests 20 21const ( 22 ES256K = "ES256K" 23 ES256 = "ES256" 24) 25 26type AtProtoSigningMethod struct { 27 alg string 28} 29 30func (m *AtProtoSigningMethod) Alg() string { 31 return m.alg 32} 33 34func (m *AtProtoSigningMethod) Verify(signingString string, signature []byte, key interface{}) error { 35 err := key.(crypto.PublicKey).HashAndVerifyLenient([]byte(signingString), signature) 36 return err 37} 38 39func (m *AtProtoSigningMethod) Sign(signingString string, key interface{}) ([]byte, error) { 40 return nil, fmt.Errorf("unimplemented") 41} 42 43func init() { 44 ES256K := AtProtoSigningMethod{alg: "ES256K"} 45 jwt.RegisterSigningMethod(ES256K.Alg(), func() jwt.SigningMethod { 46 return &ES256K 47 }) 48 49 ES256 := AtProtoSigningMethod{alg: "ES256"} 50 jwt.RegisterSigningMethod(ES256.Alg(), func() jwt.SigningMethod { 51 return &ES256 52 }) 53 54} 55 56var directory = identity.DefaultDirectory() 57 58func getRequestUserDID(r *http.Request) (string, error) { 59 headerValues := r.Header["Authorization"] 60 61 if len(headerValues) != 1 { 62 return "", fmt.Errorf("missing authorization header") 63 } 64 token := strings.TrimSpace(strings.Replace(headerValues[0], "Bearer ", "", 1)) 65 66 keyfunc := func(token *jwt.Token) (interface{}, error) { 67 did := syntax.DID(token.Claims.(jwt.MapClaims)["iss"].(string)) 68 identity, err := directory.LookupDID(r.Context(), did) 69 if err != nil { 70 return nil, fmt.Errorf("unable to resolve did %s: %s", did, err) 71 } 72 key, err := identity.PublicKey() 73 if err != nil { 74 return nil, fmt.Errorf("signing key not found for did %s: %s", did, err) 75 } 76 return key, nil 77 } 78 79 validMethods := jwt.WithValidMethods([]string{ES256, ES256K}) 80 81 parsedToken, err := jwt.ParseWithClaims(token, jwt.MapClaims{}, keyfunc, validMethods) 82 if err != nil { 83 return "", fmt.Errorf("invalid token: %s", err) 84 } 85 86 claims, ok := parsedToken.Claims.(jwt.MapClaims) 87 if !ok { 88 return "", fmt.Errorf("token contained no claims") 89 } 90 91 issVal, ok := claims["iss"].(string) 92 if !ok { 93 return "", fmt.Errorf("iss claim missing") 94 } 95 96 return string(syntax.DID(issVal)), nil 97} 98 99const ( 100 jwtCookieName = "JWT" 101 didCookieName = "DID" 102) 103 104func (s *Server) authMiddleware(next func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { 105 return func(w http.ResponseWriter, r *http.Request) { 106 jwtCookie, err := r.Cookie(jwtCookieName) 107 if err != nil { 108 slog.Error("read JWT cookie", "error", err) 109 frontend.Login("", "").Render(r.Context(), w) 110 return 111 } 112 if jwtCookie == nil { 113 slog.Error("missing JWT cookie") 114 frontend.Login("", "").Render(r.Context(), w) 115 return 116 } 117 118 didCookie, err := r.Cookie(didCookieName) 119 if err != nil { 120 slog.Error("read DID cookie", "error", err) 121 frontend.Login("", "").Render(r.Context(), w) 122 return 123 } 124 if didCookie == nil { 125 slog.Error("missing DID cookie") 126 frontend.Login("", "").Render(r.Context(), w) 127 return 128 } 129 130 claims := jwt.MapClaims{} 131 _, _, err = jwt.NewParser().ParseUnverified(jwtCookie.Value, &claims) 132 if err != nil { 133 slog.Error("parsing JWT", "error", err) 134 frontend.Login("", "").Render(r.Context(), w) 135 return 136 } 137 138 if expiry, ok := claims["exp"].(string); ok { 139 expiryInt, err := strconv.Atoi(expiry) 140 if err != nil { 141 slog.Error("invalid claims from token", "error", err) 142 frontend.Login("", "").Render(r.Context(), w) 143 return 144 } 145 146 if time.Now().Unix() > int64(expiryInt) { 147 frontend.Login("", "").Render(r.Context(), w) 148 return 149 } 150 } 151 next(w, r) 152 } 153}