A demo of a Bluesky feed generator in Go
1package feed
2
3import (
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
14const (
15 ES256K = "ES256K"
16 ES256 = "ES256"
17)
18
19func 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
32type AtProtoSigningMethod struct {
33 alg string
34}
35
36func (m *AtProtoSigningMethod) Alg() string {
37 return m.alg
38}
39
40func (m *AtProtoSigningMethod) Verify(signingString string, signature []byte, key interface{}) error {
41 err := key.(crypto.PublicKey).HashAndVerifyLenient([]byte(signingString), signature)
42 return err
43}
44
45func (m *AtProtoSigningMethod) Sign(signingString string, key interface{}) ([]byte, error) {
46 return nil, fmt.Errorf("unimplemented")
47}
48
49func 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}