[WIP] music platform user data scraper
teal-fm atproto

hack in jwt generation

Changed files
+130 -1
cmd
util
jwtgen
+10 -1
cmd/main.go
··· 18 "github.com/teal-fm/piper/service/musicbrainz" 19 "github.com/teal-fm/piper/service/spotify" 20 "github.com/teal-fm/piper/session" 21 ) 22 23 type application struct { ··· 55 // --- Service Initializations --- 56 jwksBytes, err := os.ReadFile("./jwks.json") 57 if err != nil { 58 - log.Fatalf("Error reading JWK file: %v", err) 59 } 60 jwks, err := atproto.LoadJwks(jwksBytes) 61 if err != nil {
··· 18 "github.com/teal-fm/piper/service/musicbrainz" 19 "github.com/teal-fm/piper/service/spotify" 20 "github.com/teal-fm/piper/session" 21 + "github.com/teal-fm/piper/util/jwtgen" 22 ) 23 24 type application struct { ··· 56 // --- Service Initializations --- 57 jwksBytes, err := os.ReadFile("./jwks.json") 58 if err != nil { 59 + // fuck Makefiles 60 + if err := jwtgen.WriteJwksToDisk(""); err != nil { 61 + log.Fatalf("Error reading JWK file: %v", err) 62 + } 63 + // works for now 64 + jwksBytes, err = os.ReadFile("./jwks.json") 65 + if err != nil { 66 + log.Fatalf("Error reading JWK file: %v", err) 67 + } 68 } 69 jwks, err := atproto.LoadJwks(jwksBytes) 70 if err != nil {
+120
util/jwtgen/jwtgen.go
···
··· 1 + // bless up @haileyok 2 + // https://github.com/haileyok/atproto-oauth-golang/blob/main/helpers/generic.go 3 + 4 + package jwtgen 5 + 6 + import ( 7 + "crypto/ecdsa" 8 + "crypto/elliptic" 9 + "crypto/rand" 10 + "encoding/json" 11 + "fmt" 12 + "net/url" 13 + "os" 14 + "time" 15 + 16 + "github.com/lestrrat-go/jwx/v2/jwk" 17 + ) 18 + 19 + func WriteJwksToDisk(inputPrefix string) error { 20 + var prefix *string 21 + if inputPrefix != "" { 22 + prefix = &inputPrefix 23 + } 24 + key, err := GenerateKey(prefix) 25 + if err != nil { 26 + return fmt.Errorf("error generating key: %v\n", err) 27 + } 28 + 29 + b, err := json.Marshal(key) 30 + if err != nil { 31 + return fmt.Errorf("error marshaling key: %v\n", err) 32 + } 33 + 34 + if err := os.WriteFile("./jwks.json", b, 0644); err != nil { 35 + return fmt.Errorf("error writing jwk to disk: %v\n", err) 36 + } 37 + 38 + return nil 39 + } 40 + 41 + func GenerateKey(kidPrefix *string) (jwk.Key, error) { 42 + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 43 + if err != nil { 44 + return nil, err 45 + } 46 + 47 + key, err := jwk.FromRaw(privKey) 48 + if err != nil { 49 + return nil, err 50 + } 51 + 52 + var kid string 53 + if kidPrefix != nil { 54 + kid = fmt.Sprintf("%s-%d", *kidPrefix, time.Now().Unix()) 55 + } else { 56 + kid = fmt.Sprintf("%d", time.Now().Unix()) 57 + } 58 + 59 + if err := key.Set(jwk.KeyIDKey, kid); err != nil { 60 + return nil, err 61 + } 62 + return key, nil 63 + } 64 + 65 + func IsUrlSafeAndParsed(rawString string) (*url.URL, error) { 66 + u, err := url.Parse(rawString) 67 + if err != nil { 68 + return nil, err 69 + } 70 + 71 + if u.Scheme != "https" { 72 + return nil, fmt.Errorf("input url is not https") 73 + } 74 + 75 + if u.Hostname() == "" { 76 + return nil, fmt.Errorf("url hostname was empty") 77 + } 78 + 79 + if u.User != nil { 80 + return nil, fmt.Errorf("url user was not empty") 81 + } 82 + 83 + if u.Port() != "" { 84 + return nil, fmt.Errorf("url port was not empty") 85 + } 86 + 87 + return u, nil 88 + } 89 + 90 + func GetPrivateKey(key jwk.Key) (*ecdsa.PrivateKey, error) { 91 + var pkey ecdsa.PrivateKey 92 + if err := key.Raw(&pkey); err != nil { 93 + return nil, err 94 + } 95 + 96 + return &pkey, nil 97 + } 98 + 99 + func GetPublicKey(key jwk.Key) (*ecdsa.PublicKey, error) { 100 + var pkey ecdsa.PublicKey 101 + if err := key.Raw(&pkey); err != nil { 102 + return nil, err 103 + } 104 + 105 + return &pkey, nil 106 + } 107 + 108 + type JwksResponseObject struct { 109 + Keys []jwk.Key `json:"keys"` 110 + } 111 + 112 + func CreateJwksResponseObject(key jwk.Key) *JwksResponseObject { 113 + return &JwksResponseObject{ 114 + Keys: []jwk.Key{key}, 115 + } 116 + } 117 + 118 + func ParseJWKFromBytes(bytes []byte) (jwk.Key, error) { 119 + return jwk.ParseKey(bytes) 120 + }