An implementation of the ATProto statusphere example app but in Go

refactor to use the indigo oauth library

+37 -107
auth_handlers.go
··· 2 2 3 3 import ( 4 4 _ "embed" 5 - "fmt" 6 5 "log/slog" 7 6 "net/http" 8 - "net/url" 9 7 10 - "github.com/gorilla/sessions" 11 - "github.com/willdot/statusphere-go/oauth" 8 + "github.com/bluesky-social/indigo/atproto/syntax" 12 9 ) 13 10 14 11 type LoginData struct { ··· 18 15 19 16 func (s *Server) authMiddleware(next func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { 20 17 return func(w http.ResponseWriter, r *http.Request) { 21 - _, ok := s.getDidFromSession(r) 22 - if !ok { 18 + did, _ := s.currentSessionDID(r) 19 + if did == nil { 23 20 http.Redirect(w, r, "/login", http.StatusFound) 24 21 return 25 22 } ··· 48 45 49 46 handle := r.FormValue("handle") 50 47 51 - result, err := s.oauthService.StartOAuthFlow(r.Context(), handle) 48 + redirectURL, err := s.oauthClient.StartAuthFlow(r.Context(), handle) 52 49 if err != nil { 53 50 slog.Error("starting oauth flow", "error", err) 54 51 data.Error = "error logging in" ··· 56 53 return 57 54 } 58 55 59 - u, _ := url.Parse(result.AuthorizationEndpoint) 60 - u.RawQuery = fmt.Sprintf("client_id=%s&request_uri=%s", url.QueryEscape(fmt.Sprintf("%s/client-metadata.json", s.host)), result.RequestURI) 61 - 62 - // ignore error here as it only returns an error for decoding an existing session but it will always return a session anyway which 63 - // is what we want 64 - session, _ := s.sessionStore.Get(r, "oauth-session") 65 - session.Values = map[any]any{} 66 - 67 - session.Options = &sessions.Options{ 68 - Path: "/", 69 - MaxAge: 300, // save for five minutes 70 - HttpOnly: true, 71 - } 72 - 73 - session.Values["oauth_state"] = result.State 74 - session.Values["oauth_did"] = result.DID 75 - 76 - err = session.Save(r, w) 77 - if err != nil { 78 - slog.Error("save session", "error", err) 79 - data.Error = "error logging in" 80 - tmpl.Execute(w, data) 81 - return 82 - } 83 - 84 - http.Redirect(w, r, u.String(), http.StatusFound) 56 + http.Redirect(w, r, redirectURL, http.StatusFound) 85 57 } 86 58 87 59 func (s *Server) handleOauthCallback(w http.ResponseWriter, r *http.Request) { 88 60 tmpl := s.getTemplate("login.html") 89 61 data := LoginData{} 90 62 91 - resState := r.FormValue("state") 92 - resIss := r.FormValue("iss") 93 - resCode := r.FormValue("code") 94 - 95 - session, err := s.sessionStore.Get(r, "oauth-session") 96 - if err != nil { 97 - slog.Error("getting session", "error", err) 98 - data.Error = "error logging in" 99 - tmpl.Execute(w, data) 100 - return 101 - } 102 - 103 - if resState == "" || resIss == "" || resCode == "" { 104 - slog.Error("request missing needed parameters") 105 - data.Error = "error logging in" 106 - tmpl.Execute(w, data) 107 - return 108 - } 109 - 110 - sessionState, ok := session.Values["oauth_state"].(string) 111 - if !ok { 112 - slog.Error("oauth_state not found in sesssion") 113 - data.Error = "error logging in" 114 - tmpl.Execute(w, data) 115 - return 116 - } 117 - 118 - if resState != sessionState { 119 - slog.Error("session state does not match response state") 120 - data.Error = "error logging in" 121 - tmpl.Execute(w, data) 122 - return 123 - } 124 - 125 - params := oauth.CallBackParams{ 126 - Iss: resIss, 127 - State: resState, 128 - Code: resCode, 129 - } 130 - usersDID, err := s.oauthService.OAuthCallback(r.Context(), params) 63 + sessData, err := s.oauthClient.ProcessCallback(r.Context(), r.URL.Query()) 131 64 if err != nil { 132 - slog.Error("handling oauth callback", "error", err) 65 + slog.Error("processing OAuth callback", "error", err) 133 66 data.Error = "error logging in" 134 67 tmpl.Execute(w, data) 135 68 return 136 69 } 137 70 138 - session.Options = &sessions.Options{ 139 - Path: "/", 140 - MaxAge: 86400 * 7, 141 - HttpOnly: true, 142 - } 143 - 144 - // make sure the session is empty before setting new values 145 - session.Values = map[any]any{} 146 - session.Values["did"] = usersDID 147 - 148 - err = session.Save(r, w) 149 - if err != nil { 150 - slog.Error("save session", "error", err) 71 + // create signed cookie session, indicating account DID 72 + sess, _ := s.sessionStore.Get(r, "oauth-demo") 73 + sess.Values["account_did"] = sessData.AccountDID.String() 74 + sess.Values["session_id"] = sessData.SessionID 75 + if err := sess.Save(r, w); err != nil { 76 + slog.Error("storing session data", "error", err) 151 77 data.Error = "error logging in" 152 78 tmpl.Execute(w, data) 153 79 return ··· 157 83 } 158 84 159 85 func (s *Server) HandleLogOut(w http.ResponseWriter, r *http.Request) { 160 - session, err := s.sessionStore.Get(r, "oauth-session") 161 - if err != nil { 162 - slog.Error("getting session", "error", err) 163 - http.Redirect(w, r, "/", http.StatusFound) 164 - return 165 - } 166 - 167 - did, ok := session.Values["did"] 168 - if ok { 169 - err = s.oauthService.DeleteOAuthSession(fmt.Sprintf("%s", did)) 86 + did, sessionID := s.currentSessionDID(r) 87 + if did != nil { 88 + err := s.oauthClient.Store.DeleteSession(r.Context(), *did, sessionID) 170 89 if err != nil { 171 90 slog.Error("deleting oauth session", "error", err) 172 91 } 173 92 } 174 93 175 - session.Values = map[any]any{} 176 - session.Options = &sessions.Options{ 177 - Path: "/", 178 - MaxAge: -1, 179 - HttpOnly: true, 94 + sess, _ := s.sessionStore.Get(r, "oauth-demo") 95 + sess.Values = make(map[any]any) 96 + err := sess.Save(r, w) 97 + if err != nil { 98 + http.Error(w, err.Error(), http.StatusInternalServerError) 99 + return 180 100 } 101 + http.Redirect(w, r, "/", http.StatusFound) 102 + } 181 103 182 - err = session.Save(r, w) 104 + func (s *Server) currentSessionDID(r *http.Request) (*syntax.DID, string) { 105 + sess, _ := s.sessionStore.Get(r, "oauth-demo") 106 + accountDID, ok := sess.Values["account_did"].(string) 107 + if !ok || accountDID == "" { 108 + return nil, "" 109 + } 110 + did, err := syntax.ParseDID(accountDID) 183 111 if err != nil { 184 - slog.Error("save session", "error", err) 185 - http.Redirect(w, r, "/", http.StatusFound) 186 - return 112 + return nil, "" 113 + } 114 + sessionID, ok := sess.Values["session_id"].(string) 115 + if !ok || sessionID == "" { 116 + return nil, "" 187 117 } 188 118 189 - http.Redirect(w, r, "/", http.StatusFound) 119 + return &did, sessionID 190 120 }
+19 -6
cmd/main.go
··· 3 3 import ( 4 4 "context" 5 5 "errors" 6 + "fmt" 6 7 "log" 7 8 "log/slog" 8 9 "net/http" ··· 13 14 "time" 14 15 15 16 "github.com/avast/retry-go/v4" 17 + "github.com/bluesky-social/indigo/atproto/auth/oauth" 16 18 "github.com/joho/godotenv" 17 19 "github.com/willdot/statusphere-go" 18 20 "github.com/willdot/statusphere-go/database" 19 - "github.com/willdot/statusphere-go/oauth" 20 21 ) 21 22 22 23 const ( ··· 60 61 }, 61 62 } 62 63 63 - oauthService, err := oauth.NewService(db, host, httpClient) 64 - if err != nil { 65 - slog.Error("creating new oauth service", "error", err) 66 - return 64 + var config oauth.ClientConfig 65 + bind := ":8080" 66 + scopes := []string{"atproto", "transition:generic"} 67 + if host == "" { 68 + config = oauth.NewLocalhostConfig( 69 + fmt.Sprintf("http://127.0.0.1%s/oauth/callback", bind), 70 + scopes, 71 + ) 72 + slog.Info("configuring localhost OAuth client", "CallbackURL", config.CallbackURL) 73 + } else { 74 + config = oauth.NewPublicConfig( 75 + fmt.Sprintf("%s/oauth/client-metadata.json", host), 76 + fmt.Sprintf("%s/oauth/oauth-callback", host), 77 + scopes, 78 + ) 67 79 } 80 + oauthClient := oauth.NewClientApp(&config, db) 68 81 69 - server, err := statusphere.NewServer(host, 8080, db, oauthService, httpClient) 82 + server, err := statusphere.NewServer(host, 8080, db, oauthClient, httpClient) 70 83 if err != nil { 71 84 slog.Error("create new server", "error", err) 72 85 return
+38 -18
database/oauth_requests.go
··· 1 1 package database 2 2 3 3 import ( 4 + "context" 4 5 "database/sql" 5 6 "fmt" 6 7 "log/slog" 7 8 8 - "github.com/willdot/statusphere-go/oauth" 9 + "github.com/bluesky-social/indigo/atproto/auth/oauth" 10 + "github.com/bluesky-social/indigo/atproto/syntax" 9 11 ) 10 12 11 13 func createOauthRequestsTable(db *sql.DB) error { 12 14 createOauthRequestsTableSQL := `CREATE TABLE IF NOT EXISTS oauthrequests ( 13 15 "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, 14 - "authserverIss" TEXT, 15 16 "state" TEXT, 16 - "did" TEXT, 17 - "pdsUrl" TEXT, 17 + "authServerURL" TEXT, 18 + "accountDID" TEXT, 19 + "scope" TEXT, 20 + "requestURI" TEXT, 21 + "authServerTokenEndpoint" TEXT, 18 22 "pkceVerifier" TEXT, 19 23 "dpopAuthserverNonce" TEXT, 20 - "dpopPrivateJwk" TEXT, 21 - UNIQUE(did,state) 24 + "dpopPrivateKeyMultibase" TEXT, 25 + UNIQUE(state) 22 26 );` 23 27 24 28 slog.Info("Create oauthrequests table...") ··· 35 39 return nil 36 40 } 37 41 38 - func (d *DB) CreateOauthRequest(request oauth.Request) error { 39 - sql := `INSERT INTO oauthrequests (authserverIss, state, did, pdsUrl, pkceVerifier, dpopAuthServerNonce, dpopPrivateJwk) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(did,state) DO NOTHING;` 40 - _, err := d.db.Exec(sql, request.AuthserverIss, request.State, request.Did, request.PdsURL, request.PkceVerifier, request.DpopAuthserverNonce, request.DpopPrivateJwk) 42 + func (d *DB) SaveAuthRequestInfo(ctx context.Context, info oauth.AuthRequestData) error { 43 + did := "" 44 + if info.AccountDID != nil { 45 + did = info.AccountDID.String() 46 + } 47 + 48 + sql := `INSERT INTO oauthrequests (state, authServerURL, accountDID, scope, requestURI, authServerTokenEndpoint, pkceVerifier, dpopAuthserverNonce, dpopPrivateKeyMultibase) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(state) DO NOTHING;` 49 + _, err := d.db.Exec(sql, info.State, info.AuthServerURL, did, info.Scope, info.RequestURI, info.AuthServerTokenEndpoint, info.PKCEVerifier, info.DPoPAuthServerNonce, info.DPoPPrivateKeyMultibase) 41 50 if err != nil { 51 + slog.Error("saving auth request info", "error", err) 42 52 return fmt.Errorf("exec insert oauth request: %w", err) 43 53 } 44 54 45 55 return nil 46 56 } 47 57 48 - func (d *DB) GetOauthRequest(state string) (oauth.Request, error) { 49 - var oauthRequest oauth.Request 50 - sql := "SELECT authserverIss, state, did, pdsUrl, pkceVerifier, dpopAuthServerNonce, dpopPrivateJwk FROM oauthrequests WHERE state = ?;" 58 + func (d *DB) GetAuthRequestInfo(ctx context.Context, state string) (*oauth.AuthRequestData, error) { 59 + var oauthRequest oauth.AuthRequestData 60 + sql := "SELECT state, authServerURL, accountDID, scope, requestURI, authServerTokenEndpoint, pkceVerifier, dpopAuthserverNonce, dpopPrivateKeyMultibase FROM oauthrequests where state = ?;" 51 61 rows, err := d.db.Query(sql, state) 52 62 if err != nil { 53 - return oauthRequest, fmt.Errorf("run query to get oauth request: %w", err) 63 + return nil, fmt.Errorf("run query to get oauth request: %w", err) 54 64 } 55 65 defer rows.Close() 56 66 67 + var did string 68 + 57 69 for rows.Next() { 58 - if err := rows.Scan(&oauthRequest.AuthserverIss, &oauthRequest.State, &oauthRequest.Did, &oauthRequest.PdsURL, &oauthRequest.PkceVerifier, &oauthRequest.DpopAuthserverNonce, &oauthRequest.DpopPrivateJwk); err != nil { 59 - return oauthRequest, fmt.Errorf("scan row: %w", err) 70 + if err := rows.Scan(&oauthRequest.State, &oauthRequest.AuthServerURL, &did, &oauthRequest.Scope, &oauthRequest.RequestURI, &oauthRequest.AuthServerTokenEndpoint, &oauthRequest.PKCEVerifier, &oauthRequest.DPoPAuthServerNonce, &oauthRequest.DPoPPrivateKeyMultibase); err != nil { 71 + return nil, fmt.Errorf("scan row: %w", err) 60 72 } 61 73 62 - return oauthRequest, nil 74 + if did != "" { 75 + parsedDID, err := syntax.ParseDID(did) 76 + if err != nil { 77 + return nil, fmt.Errorf("invalid DID stored in record: %w", err) 78 + } 79 + oauthRequest.AccountDID = &parsedDID 80 + } 81 + 82 + return &oauthRequest, nil 63 83 } 64 - return oauthRequest, fmt.Errorf("not found") 84 + return nil, fmt.Errorf("not found") 65 85 } 66 86 67 - func (d *DB) DeleteOauthRequest(state string) error { 87 + func (d *DB) DeleteAuthRequestInfo(ctx context.Context, state string) error { 68 88 sql := "DELETE FROM oauthrequests WHERE state = ?;" 69 89 _, err := d.db.Exec(sql, state) 70 90 if err != nil {
+45 -42
database/oauth_sessions.go
··· 1 1 package database 2 2 3 3 import ( 4 + "context" 4 5 "database/sql" 6 + "encoding/json" 5 7 "fmt" 6 8 "log/slog" 7 9 8 - "github.com/willdot/statusphere-go/oauth" 10 + "github.com/bluesky-social/indigo/atproto/auth/oauth" 11 + "github.com/bluesky-social/indigo/atproto/syntax" 9 12 ) 10 13 11 14 func createOauthSessionsTable(db *sql.DB) error { 12 15 createOauthSessionsTableSQL := `CREATE TABLE IF NOT EXISTS oauthsessions ( 13 16 "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, 14 - "did" TEXT, 15 - "pdsUrl" TEXT, 16 - "authserverIss" TEXT, 17 + "accountDID" TEXT, 18 + "sessionID" TEXT, 19 + "hostURL" TEXT, 20 + "authServerURL" TEXT, 21 + "authServerTokenEndpoint" TEXT, 22 + "scopes" TEXT, 17 23 "accessToken" TEXT, 18 24 "refreshToken" TEXT, 19 - "dpopPdsNonce" TEXT, 20 - "dpopAuthserverNonce" TEXT, 21 - "dpopPrivateJwk" TEXT, 22 - "expiration" integer, 23 - UNIQUE(did) 25 + "dpopAuthServerNonce" TEXT, 26 + "dpopHostNonce" TEXT, 27 + "dpopPrivateKeyMultibase" TEXT, 28 + UNIQUE(accountDID) 24 29 );` 25 30 26 31 slog.Info("Create oauthsessions table...") ··· 37 42 return nil 38 43 } 39 44 40 - func (d *DB) CreateOauthSession(session oauth.Session) error { 41 - sql := `INSERT INTO oauthsessions (did, pdsUrl, authserverIss, accessToken, refreshToken, dpopPdsNonce, dpopAuthserverNonce, dpopPrivateJwk, expiration) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(did) DO NOTHING;` // TODO: update on conflict 42 - _, err := d.db.Exec(sql, session.Did, session.PdsUrl, session.AuthserverIss, session.AccessToken, session.RefreshToken, session.DpopPdsNonce, session.DpopAuthserverNonce, session.DpopPrivateJwk, session.Expiration) 45 + func (d *DB) SaveSession(ctx context.Context, sess oauth.ClientSessionData) error { 46 + scopes, err := json.Marshal(sess.Scopes) 43 47 if err != nil { 48 + return fmt.Errorf("marshalling scopes: %w", err) 49 + } 50 + 51 + slog.Info("session to save", "did", sess.AccountDID.String(), "session id", sess.SessionID) 52 + 53 + sql := `INSERT INTO oauthsessions (accountDID, sessionID, hostURL, authServerURL, authServerTokenEndpoint, scopes, accessToken, refreshToken, dpopAuthServerNonce, dpopHostNonce, dpopPrivateKeyMultibase) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(accountDID) DO NOTHING;` // TODO: update on conflict 54 + _, err = d.db.Exec(sql, sess.AccountDID.String(), sess.SessionID, sess.HostURL, sess.AuthServerURL, sess.AuthServerTokenEndpoint, string(scopes), sess.AccessToken, sess.RefreshToken, sess.DPoPAuthServerNonce, sess.DPoPHostNonce, sess.DPoPPrivateKeyMultibase) 55 + if err != nil { 56 + slog.Error("saving session", "error", err) 44 57 return fmt.Errorf("exec insert oauth session: %w", err) 45 58 } 46 59 47 60 return nil 48 61 } 49 62 50 - func (d *DB) GetOauthSession(did string) (oauth.Session, error) { 51 - var session oauth.Session 52 - sql := "SELECT * FROM oauthsessions WHERE did = ?;" 53 - rows, err := d.db.Query(sql, did) 63 + func (d *DB) GetSession(ctx context.Context, did syntax.DID, sessionID string) (*oauth.ClientSessionData, error) { 64 + var session oauth.ClientSessionData 65 + sql := "SELECT hostURL, authServerURL, authServerTokenEndpoint, scopes, accessToken, refreshToken, dpopAuthServerNonce, dpopHostNonce, dpopPrivateKeyMultibase FROM oauthsessions where accountDID = ? AND sessionID = ?;" 66 + rows, err := d.db.Query(sql, did.String(), sessionID) 54 67 if err != nil { 55 - return session, fmt.Errorf("run query to get oauth session: %w", err) 68 + return nil, fmt.Errorf("run query to get oauth session: %w", err) 56 69 } 57 70 defer rows.Close() 58 71 72 + scopes := "" 59 73 for rows.Next() { 60 - if err := rows.Scan(&session.ID, &session.Did, &session.PdsUrl, &session.AuthserverIss, &session.AccessToken, &session.RefreshToken, &session.DpopPdsNonce, &session.DpopAuthserverNonce, &session.DpopPrivateJwk, &session.Expiration); err != nil { 61 - return session, fmt.Errorf("scan row: %w", err) 74 + if err := rows.Scan(&session.HostURL, &session.AuthServerURL, &session.AuthServerTokenEndpoint, &scopes, &session.AccessToken, &session.RefreshToken, &session.DPoPAuthServerNonce, &session.DPoPHostNonce, &session.DPoPPrivateKeyMultibase); err != nil { 75 + return nil, fmt.Errorf("scan row: %w", err) 62 76 } 77 + session.AccountDID = did 63 78 64 - return session, nil 65 - } 66 - return session, fmt.Errorf("not found") 67 - } 79 + var parsedScopes []string 80 + err = json.Unmarshal([]byte(scopes), &parsedScopes) 81 + if err != nil { 82 + return nil, fmt.Errorf("parsing scopes: %w", err) 83 + } 68 84 69 - func (d *DB) UpdateOauthSession(accessToken, refreshToken, dpopAuthServerNonce, did string, expiration int64) error { 70 - sql := `UPDATE oauthsessions SET accessToken = ?, refreshToken = ?, dpopAuthserverNonce = ?, expiration = ? where did = ?` 71 - _, err := d.db.Exec(sql, accessToken, refreshToken, dpopAuthServerNonce, expiration, did) 72 - if err != nil { 73 - return fmt.Errorf("exec update oauth session: %w", err) 74 - } 85 + session.Scopes = parsedScopes 75 86 76 - return nil 77 - } 78 - 79 - func (d *DB) UpdateOauthSessionDpopPdsNonce(dpopPdsServerNonce, did string) error { 80 - sql := `UPDATE oauthsessions SET dpopPdsNonce = ? where did = ?` 81 - _, err := d.db.Exec(sql, dpopPdsServerNonce, did) 82 - if err != nil { 83 - return fmt.Errorf("exec update oauth session dpop pds nonce: %w", err) 87 + return &session, nil 84 88 } 85 - 86 - return nil 89 + return nil, fmt.Errorf("not found") 87 90 } 88 91 89 - func (d *DB) DeleteOauthSession(did string) error { 90 - sql := "DELETE FROM oauthsessions WHERE did = ?;" 91 - _, err := d.db.Exec(sql, did) 92 + func (d *DB) DeleteSession(ctx context.Context, did syntax.DID, sessionID string) error { 93 + sql := "DELETE FROM oauthsessions WHERE accountDID = ?;" 94 + _, err := d.db.Exec(sql, did.String()) 92 95 if err != nil { 93 96 return fmt.Errorf("exec delete oauth session: %w", err) 94 97 }
+19 -44
go.mod
··· 6 6 7 7 require ( 8 8 github.com/avast/retry-go/v4 v4.6.1 9 + github.com/bluesky-social/indigo v0.0.0-20250813051257-8be102876fb7 9 10 github.com/bluesky-social/jetstream v0.0.0-20250414024304-d17bd81a945e 10 11 github.com/glebarez/go-sqlite v1.22.0 11 12 github.com/gorilla/sessions v1.4.0 12 - github.com/haileyok/atproto-oauth-golang v0.0.2 13 13 github.com/joho/godotenv v1.5.1 14 - github.com/lestrrat-go/jwx/v2 v2.0.12 15 14 ) 16 15 17 16 require ( 18 17 github.com/beorn7/perks v1.0.1 // indirect 19 - github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188 // indirect 20 18 github.com/carlmjohnson/versioninfo v0.22.5 // indirect 21 19 github.com/cespare/xxhash/v2 v2.3.0 // indirect 22 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect 20 + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 23 21 github.com/dustin/go-humanize v1.0.1 // indirect 24 - github.com/felixge/httpsnoop v1.0.4 // indirect 25 - github.com/go-logr/logr v1.4.2 // indirect 26 - github.com/go-logr/stdr v1.2.2 // indirect 27 22 github.com/goccy/go-json v0.10.3 // indirect 28 - github.com/gogo/protobuf v1.3.2 // indirect 29 - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect 23 + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect 24 + github.com/google/go-querystring v1.1.0 // indirect 30 25 github.com/google/uuid v1.6.0 // indirect 31 26 github.com/gorilla/securecookie v1.1.2 // indirect 32 27 github.com/gorilla/websocket v1.5.1 // indirect 33 - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 34 - github.com/hashicorp/go-retryablehttp v0.7.5 // indirect 35 - github.com/hashicorp/golang-lru v1.0.2 // indirect 36 - github.com/ipfs/bbloom v0.0.4 // indirect 37 - github.com/ipfs/go-block-format v0.2.0 // indirect 28 + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 38 29 github.com/ipfs/go-cid v0.4.1 // indirect 39 - github.com/ipfs/go-datastore v0.6.0 // indirect 40 - github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect 41 - github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect 42 - github.com/ipfs/go-ipfs-util v0.0.3 // indirect 43 - github.com/ipfs/go-ipld-cbor v0.1.0 // indirect 44 - github.com/ipfs/go-ipld-format v0.6.0 // indirect 45 - github.com/ipfs/go-log v1.0.5 // indirect 46 - github.com/ipfs/go-log/v2 v2.5.1 // indirect 47 - github.com/ipfs/go-metrics-interface v0.0.1 // indirect 48 - github.com/jbenet/goprocess v0.1.4 // indirect 49 - github.com/klauspost/compress v1.17.9 // indirect 30 + github.com/klauspost/compress v1.18.0 // indirect 50 31 github.com/klauspost/cpuid/v2 v2.2.7 // indirect 51 - github.com/lestrrat-go/blackmagic v1.0.2 // indirect 52 - github.com/lestrrat-go/httpcc v1.0.1 // indirect 53 - github.com/lestrrat-go/httprc v1.0.4 // indirect 54 - github.com/lestrrat-go/iter v1.0.2 // indirect 55 - github.com/lestrrat-go/option v1.0.1 // indirect 56 32 github.com/mattn/go-isatty v0.0.20 // indirect 57 33 github.com/minio/sha256-simd v1.0.1 // indirect 58 34 github.com/mr-tron/base58 v1.2.0 // indirect ··· 61 37 github.com/multiformats/go-multibase v0.2.0 // indirect 62 38 github.com/multiformats/go-multihash v0.2.3 // indirect 63 39 github.com/multiformats/go-varint v0.0.7 // indirect 64 - github.com/opentracing/opentracing-go v1.2.0 // indirect 65 - github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 66 - github.com/prometheus/client_golang v1.19.1 // indirect 67 - github.com/prometheus/client_model v0.6.1 // indirect 68 - github.com/prometheus/common v0.54.0 // indirect 69 - github.com/prometheus/procfs v0.15.1 // indirect 40 + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 41 + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 42 + github.com/prometheus/client_golang v1.23.0 // indirect 43 + github.com/prometheus/client_model v0.6.2 // indirect 44 + github.com/prometheus/common v0.65.0 // indirect 45 + github.com/prometheus/procfs v0.17.0 // indirect 70 46 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 71 - github.com/segmentio/asm v1.2.0 // indirect 72 47 github.com/spaolacci/murmur3 v1.1.0 // indirect 73 48 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e // indirect 74 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect 49 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 50 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 75 51 go.opentelemetry.io/otel v1.29.0 // indirect 76 52 go.opentelemetry.io/otel/metric v1.29.0 // indirect 77 53 go.opentelemetry.io/otel/trace v1.29.0 // indirect 78 54 go.uber.org/atomic v1.11.0 // indirect 79 - go.uber.org/multierr v1.11.0 // indirect 80 - go.uber.org/zap v1.26.0 // indirect 81 - golang.org/x/crypto v0.32.0 // indirect 82 - golang.org/x/net v0.33.0 // indirect 83 - golang.org/x/sys v0.29.0 // indirect 55 + golang.org/x/crypto v0.41.0 // indirect 56 + golang.org/x/net v0.42.0 // indirect 57 + golang.org/x/sys v0.35.0 // indirect 58 + golang.org/x/time v0.12.0 // indirect 84 59 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect 85 - google.golang.org/protobuf v1.34.2 // indirect 60 + google.golang.org/protobuf v1.36.7 // indirect 86 61 lukechampine.com/blake3 v1.2.1 // indirect 87 62 modernc.org/libc v1.37.6 // indirect 88 63 modernc.org/mathutil v1.6.0 // indirect
+39 -192
go.sum
··· 1 - github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 1 github.com/avast/retry-go/v4 v4.6.1 h1:VkOLRubHdisGrHnTu89g08aQEWEgRU7LVEop3GbIcMk= 3 2 github.com/avast/retry-go/v4 v4.6.1/go.mod h1:V6oF8njAwxJ5gRo1Q7Cxab24xs5NCWZBeaHHBklR8mA= 4 - github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 5 3 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 6 4 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 7 - github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188 h1:1sQaG37xk08/rpmdhrmMkfQWF9kZbnfHm9Zav3bbSMk= 8 - github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188/go.mod h1:NVBwZvbBSa93kfyweAmKwOLYawdVHdwZ9s+GZtBBVLA= 5 + github.com/bluesky-social/indigo v0.0.0-20250813051257-8be102876fb7 h1:FyoGfQFw/cTkDHdUTIYIHxfyUDgRS12K4o1mYC3ovRs= 6 + github.com/bluesky-social/indigo v0.0.0-20250813051257-8be102876fb7/go.mod h1:n6QE1NDPFoi7PRbMUZmc2y7FibCqiVU4ePpsvhHUBR8= 9 7 github.com/bluesky-social/jetstream v0.0.0-20250414024304-d17bd81a945e h1:P/O6TDHs53gwgV845uDHI+Nri889ixksRrh4bCkCdxo= 10 8 github.com/bluesky-social/jetstream v0.0.0-20250414024304-d17bd81a945e/go.mod h1:WiYEeyJSdUwqoaZ71KJSpTblemUCpwJfh5oVXplK6T4= 11 9 github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc= 12 10 github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8= 13 11 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 14 12 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 15 - github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 16 - github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 - github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 13 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 19 14 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 - github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 21 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 22 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= 23 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= 24 15 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 25 16 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 26 17 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 27 18 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 28 19 github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= 29 20 github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= 30 - github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 31 21 github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 32 22 github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 33 23 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 34 24 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 35 - github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= 36 - github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 37 25 github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= 38 26 github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 39 27 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 40 28 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 41 - github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= 42 - github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 43 - github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 44 - github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 29 + github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= 30 + github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= 31 + github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 32 + github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 33 + github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 34 + github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 35 + github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 45 36 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 46 37 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 47 38 github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= 48 39 github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= 49 - github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 50 40 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 51 41 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 52 - github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 53 - github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 54 42 github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 55 43 github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 56 44 github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= 57 45 github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= 58 46 github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 59 47 github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 60 - github.com/haileyok/atproto-oauth-golang v0.0.2 h1:61KPkLB615LQXR2f5x1v3sf6vPe6dOXqNpTYCgZ0Fz8= 61 - github.com/haileyok/atproto-oauth-golang v0.0.2/go.mod h1:jcZ4GCjo5I5RuE/RsAXg1/b6udw7R4W+2rb/cGyTDK8= 62 48 github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 63 49 github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 64 - github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= 65 - github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 66 50 github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= 67 51 github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= 68 52 github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 69 53 github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 54 + github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 55 + github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 70 56 github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= 71 57 github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= 72 58 github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= ··· 75 61 github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= 76 62 github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= 77 63 github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= 78 - github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= 79 - github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= 80 64 github.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ= 81 65 github.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE= 82 66 github.com/ipfs/go-ipfs-ds-help v1.1.1 h1:B5UJOH52IbcfS56+Ul+sv8jnIV10lbjLF5eOO0C66Nw= ··· 89 73 github.com/ipfs/go-ipld-format v0.6.0/go.mod h1:g4QVMTn3marU3qXchwjpKPKgJv+zF+OlaKMyhJ4LHPg= 90 74 github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= 91 75 github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= 92 - github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= 93 76 github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= 94 77 github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= 95 78 github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= 96 79 github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= 97 - github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= 98 80 github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= 99 81 github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= 100 82 github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 101 83 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 102 - github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 103 - github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 104 - github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 105 - github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 106 - github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 107 - github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 84 + github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 85 + github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 108 86 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 109 87 github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 110 - github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 111 - github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 112 - github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 113 - github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 114 - github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 115 - github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 116 - github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 117 - github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= 118 - github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= 119 - github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= 120 - github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= 121 - github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= 122 - github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= 123 - github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= 124 - github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= 125 - github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= 126 - github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluSDWeA= 127 - github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ= 128 - github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 129 - github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= 130 - github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 131 - github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 132 88 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 133 89 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 134 90 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= ··· 145 101 github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= 146 102 github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= 147 103 github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= 104 + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 105 + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 148 106 github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 149 107 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 150 - github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 151 - github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 152 108 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 153 109 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 154 110 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= 155 111 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= 156 - github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= 157 - github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= 158 - github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 159 - github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 160 - github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8= 161 - github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ= 162 - github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 163 - github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 112 + github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= 113 + github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= 114 + github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 115 + github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 116 + github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= 117 + github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= 118 + github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= 119 + github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= 164 120 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 165 121 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 166 - github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 167 - github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 168 - github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 169 - github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 170 - github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 171 - github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 172 - github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 173 - github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= 174 - github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 175 - github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= 176 - github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= 177 122 github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 178 123 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 179 - github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 180 - github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 181 - github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 182 - github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 183 - github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 184 - github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 185 - github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 186 - github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 187 - github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 188 - github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 189 - github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 190 124 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 191 125 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 192 - github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 193 - github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= 194 - github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= 195 126 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4= 196 127 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 197 - github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 198 - github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 199 - github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 200 - github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 128 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= 129 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 130 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= 131 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= 201 132 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= 202 133 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= 203 134 go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= ··· 206 137 go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= 207 138 go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= 208 139 go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= 209 - go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 210 - go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 211 140 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 212 141 go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 213 - go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 214 - go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= 215 - go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= 216 - go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 217 - go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 142 + go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 143 + go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 218 144 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 219 145 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 220 - go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 221 - go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= 222 - go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 223 146 go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 224 147 go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 225 - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 226 - golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 227 - golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 228 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 229 - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 230 - golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= 231 - golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= 232 - golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 233 - golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 234 - golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 235 - golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 236 - golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 237 - golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 238 - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 239 - golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 240 - golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 241 - golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 242 - golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 243 - golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 244 - golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 245 - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 246 - golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 247 - golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 248 - golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 249 - golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 250 - golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 251 - golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 252 - golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 253 - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 254 - golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 255 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 256 - golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 257 - golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 258 - golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 259 - golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 260 - golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 261 - golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 262 - golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 263 - golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 264 - golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 265 - golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 266 - golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 267 - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 268 - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 148 + golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= 149 + golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= 150 + golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= 151 + golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= 269 152 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 270 153 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 271 - golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 272 - golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 273 - golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 274 - golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 275 - golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 276 - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 277 - golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 278 - golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 279 - golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= 280 - golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 281 - golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 282 - golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 283 - golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 284 - golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 285 - golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 286 - golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 287 - golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 288 - golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 289 - golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 290 - golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 291 - golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 292 - golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 293 - golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 294 - golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 295 - golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 296 - golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 297 - golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 298 - golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 299 - golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 154 + golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= 155 + golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 156 + golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= 157 + golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 300 158 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 301 - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 302 159 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 303 160 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 304 - google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 305 - google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 306 - gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 307 - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 308 - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 309 - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 310 - gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 311 - gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 312 - gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 313 - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 314 - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 161 + google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= 162 + google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 315 163 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 316 164 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 317 - honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 318 165 lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= 319 166 lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= 320 167 modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw=
+36 -21
home_handler.go
··· 56 56 data := HomeData{ 57 57 AvailableStatus: Availablestatus, 58 58 } 59 - usersDid, ok := s.getDidFromSession(r) 60 - if ok { 61 - profile, err := s.getUserProfileForDid(usersDid) 59 + 60 + did, _ := s.currentSessionDID(r) 61 + if did != nil { 62 + profile, err := s.getUserProfileForDid(did.String()) 62 63 if err != nil { 63 64 slog.Error("getting logged in users profile", "error", err) 64 65 } ··· 107 108 return 108 109 } 109 110 110 - did, ok := s.getDidFromSession(r) 111 - if !ok { 112 - http.Error(w, "failed to get did from session", http.StatusBadRequest) 111 + did, sessionID := s.currentSessionDID(r) 112 + if did == nil { 113 + http.Redirect(w, r, "/login", http.StatusFound) 113 114 return 114 115 } 115 116 116 - oauthSession, err := s.oauthService.GetOauthSession(r.Context(), did) 117 + slog.Info("session", "did", did.String(), "session id", sessionID) 118 + 119 + oauthSess, err := s.oauthClient.ResumeSession(r.Context(), *did, sessionID) 117 120 if err != nil { 118 - http.Error(w, "failed to get oauth session", http.StatusInternalServerError) 121 + http.Error(w, "not authenticated", http.StatusUnauthorized) 119 122 return 120 123 } 124 + c := oauthSess.APIClient() 121 125 122 126 createdAt := time.Now() 123 - uri, err := s.CreateNewStatus(r.Context(), oauthSession, status, createdAt) 127 + 128 + bodyReq := map[string]any{ 129 + "repo": c.AccountDID.String(), 130 + "collection": "xyz.statusphere.status", 131 + "record": map[string]any{ 132 + "status": status, 133 + "createdAt": createdAt, 134 + }, 135 + } 136 + var result CreateRecordResp 137 + err = c.Post(r.Context(), "com.atproto.repo.createRecord", bodyReq, &result) 124 138 if err != nil { 125 139 slog.Error("failed to create new status", "error", err) 140 + http.Redirect(w, r, "/", http.StatusFound) 141 + return 126 142 } 127 143 128 - if uri != "" { 129 - statusToStore := Status{ 130 - URI: uri, 131 - Did: did, 132 - Status: status, 133 - CreatedAt: createdAt.UnixMilli(), 134 - IndexedAt: time.Now().UnixMilli(), 135 - } 136 - err = s.store.CreateStatus(statusToStore) 137 - if err != nil { 138 - slog.Error("failed to store status that has been created", "error", err) 139 - } 144 + statusToStore := Status{ 145 + URI: result.URI, 146 + Did: c.AccountDID.String(), 147 + Status: status, 148 + CreatedAt: createdAt.UnixMilli(), 149 + IndexedAt: time.Now().UnixMilli(), 150 + } 151 + 152 + err = s.store.CreateStatus(statusToStore) 153 + if err != nil { 154 + slog.Error("failed to store status that has been created", "error", err) 140 155 } 141 156 142 157 http.Redirect(w, r, "/", http.StatusFound)
-408
oauth/service.go
··· 1 - package oauth 2 - 3 - import ( 4 - "context" 5 - "encoding/base64" 6 - "encoding/json" 7 - "fmt" 8 - "io" 9 - "net/http" 10 - "net/url" 11 - "os" 12 - "strings" 13 - "time" 14 - 15 - atoauth "github.com/haileyok/atproto-oauth-golang" 16 - oauthhelpers "github.com/haileyok/atproto-oauth-golang/helpers" 17 - "github.com/lestrrat-go/jwx/v2/jwk" 18 - ) 19 - 20 - const ( 21 - scope = "atproto transition:generic" 22 - ) 23 - 24 - type Request struct { 25 - ID uint 26 - AuthserverIss string 27 - State string 28 - Did string 29 - PdsURL string 30 - PkceVerifier string 31 - DpopAuthserverNonce string 32 - DpopPrivateJwk string 33 - } 34 - 35 - type Session struct { 36 - ID uint 37 - Did string 38 - PdsUrl string 39 - AuthserverIss string 40 - AccessToken string 41 - RefreshToken string 42 - DpopPdsNonce string 43 - DpopAuthserverNonce string 44 - DpopPrivateJwk string 45 - Expiration int64 46 - } 47 - 48 - func (s *Session) CreatePrivateKey() (jwk.Key, error) { 49 - privateJwk, err := oauthhelpers.ParseJWKFromBytes([]byte(s.DpopPrivateJwk)) 50 - if err != nil { 51 - return nil, fmt.Errorf("create private jwk: %w", err) 52 - } 53 - return privateJwk, nil 54 - } 55 - 56 - type OAuthFlowResult struct { 57 - AuthorizationEndpoint string 58 - State string 59 - DID string 60 - RequestURI string 61 - } 62 - 63 - type CallBackParams struct { 64 - State string 65 - Iss string 66 - Code string 67 - } 68 - 69 - type Store interface { 70 - CreateOauthRequest(request Request) error 71 - GetOauthRequest(state string) (Request, error) 72 - DeleteOauthRequest(state string) error 73 - CreateOauthSession(session Session) error 74 - GetOauthSession(did string) (Session, error) 75 - UpdateOauthSession(accessToken, refreshToken, dpopAuthServerNonce, did string, expiration int64) error 76 - DeleteOauthSession(did string) error 77 - UpdateOauthSessionDpopPdsNonce(dpopPdsServerNonce, did string) error 78 - } 79 - 80 - type Service struct { 81 - store Store 82 - oauthClient *atoauth.Client 83 - httpClient *http.Client 84 - jwks *JWKS 85 - } 86 - 87 - func NewService(store Store, serverBase string, httpClient *http.Client) (*Service, error) { 88 - jwks, err := getJWKS() 89 - if err != nil { 90 - return nil, fmt.Errorf("getting JWKS: %w", err) 91 - } 92 - 93 - oauthClient, err := createOauthClient(jwks, serverBase, httpClient) 94 - if err != nil { 95 - return nil, fmt.Errorf("create oauth client: %w", err) 96 - } 97 - 98 - return &Service{ 99 - store: store, 100 - oauthClient: oauthClient, 101 - httpClient: httpClient, 102 - jwks: jwks, 103 - }, nil 104 - } 105 - 106 - func (s *Service) StartOAuthFlow(ctx context.Context, handle string) (*OAuthFlowResult, error) { 107 - usersDID, err := s.resolveHandle(handle) 108 - if err != nil { 109 - return nil, fmt.Errorf("resolve handle: %w", err) 110 - } 111 - 112 - dpopPrivateKey, err := oauthhelpers.GenerateKey(nil) 113 - if err != nil { 114 - return nil, fmt.Errorf("generate private key: %w", err) 115 - } 116 - 117 - parResp, meta, service, err := s.makeOAuthRequest(ctx, usersDID, handle, dpopPrivateKey) 118 - if err != nil { 119 - return nil, fmt.Errorf("make oauth request: %w", err) 120 - } 121 - 122 - dpopPrivateKeyJson, err := json.Marshal(dpopPrivateKey) 123 - if err != nil { 124 - return nil, fmt.Errorf("marshal dpop private key: %w", err) 125 - } 126 - 127 - oauthRequst := Request{ 128 - AuthserverIss: meta.Issuer, 129 - State: parResp.State, 130 - Did: usersDID, 131 - PkceVerifier: parResp.PkceVerifier, 132 - DpopAuthserverNonce: parResp.DpopAuthserverNonce, 133 - DpopPrivateJwk: string(dpopPrivateKeyJson), 134 - PdsURL: service, 135 - } 136 - err = s.store.CreateOauthRequest(oauthRequst) 137 - if err != nil { 138 - return nil, fmt.Errorf("store oauth request: %w", err) 139 - } 140 - 141 - result := OAuthFlowResult{ 142 - AuthorizationEndpoint: meta.AuthorizationEndpoint, 143 - State: parResp.State, 144 - DID: usersDID, 145 - RequestURI: parResp.RequestUri, 146 - } 147 - 148 - return &result, nil 149 - } 150 - 151 - func (s *Service) OAuthCallback(ctx context.Context, params CallBackParams) (string, error) { 152 - oauthRequest, err := s.store.GetOauthRequest(fmt.Sprintf("%s", params.State)) 153 - if err != nil { 154 - return "", fmt.Errorf("get oauth request from store: %w", err) 155 - } 156 - 157 - err = s.store.DeleteOauthRequest(fmt.Sprintf("%s", params.State)) 158 - if err != nil { 159 - return "", fmt.Errorf("delete oauth request from store: %w", err) 160 - } 161 - 162 - jwk, err := oauthhelpers.ParseJWKFromBytes([]byte(oauthRequest.DpopPrivateJwk)) 163 - if err != nil { 164 - return "", fmt.Errorf("parse dpop private key: %w", err) 165 - } 166 - 167 - initialTokenResp, err := s.oauthClient.InitialTokenRequest(ctx, params.Code, params.Iss, oauthRequest.PkceVerifier, oauthRequest.DpopAuthserverNonce, jwk) 168 - if err != nil { 169 - return "", fmt.Errorf("make oauth token request: %w", err) 170 - } 171 - 172 - if initialTokenResp.Scope != scope { 173 - return "", fmt.Errorf("incorrect scope from token request") 174 - } 175 - 176 - oauthSession := Session{ 177 - Did: oauthRequest.Did, 178 - PdsUrl: oauthRequest.PdsURL, 179 - AuthserverIss: oauthRequest.AuthserverIss, 180 - AccessToken: initialTokenResp.AccessToken, 181 - RefreshToken: initialTokenResp.RefreshToken, 182 - DpopAuthserverNonce: initialTokenResp.DpopAuthserverNonce, 183 - DpopPrivateJwk: oauthRequest.DpopPrivateJwk, 184 - Expiration: time.Now().Add(time.Duration(int(time.Second) * int(initialTokenResp.ExpiresIn))).UnixMilli(), 185 - } 186 - 187 - err = s.store.CreateOauthSession(oauthSession) 188 - if err != nil { 189 - return "", fmt.Errorf("create oauth session in store: %w", err) 190 - } 191 - return oauthRequest.Did, nil 192 - } 193 - 194 - func (s *Service) GetOauthSession(ctx context.Context, did string) (Session, error) { 195 - session, err := s.store.GetOauthSession(did) 196 - if err != nil { 197 - return Session{}, fmt.Errorf("find oauth session: %w", err) 198 - } 199 - 200 - // if the session expires in more than 5 minutes, return it 201 - if session.Expiration > time.Now().Add(time.Minute*5).UnixMilli() { 202 - return session, nil 203 - } 204 - 205 - // refresh the session 206 - privateJwk, err := oauthhelpers.ParseJWKFromBytes([]byte(session.DpopPrivateJwk)) 207 - if err != nil { 208 - return Session{}, fmt.Errorf("parse sessions private JWK: %w", err) 209 - } 210 - 211 - resp, err := s.oauthClient.RefreshTokenRequest(ctx, session.RefreshToken, session.AuthserverIss, session.DpopAuthserverNonce, privateJwk) 212 - if err != nil { 213 - return Session{}, fmt.Errorf("refresh token: %w", err) 214 - } 215 - 216 - expiration := time.Now().Add(time.Duration(int(time.Second) * int(resp.ExpiresIn))).UnixMilli() 217 - 218 - err = s.store.UpdateOauthSession(resp.AccessToken, resp.RefreshToken, resp.DpopAuthserverNonce, did, expiration) 219 - if err != nil { 220 - return Session{}, fmt.Errorf("update session after refresh: %w", err) 221 - } 222 - 223 - session.AccessToken = resp.AccessToken 224 - session.RefreshToken = resp.RefreshToken 225 - session.DpopAuthserverNonce = resp.DpopAuthserverNonce 226 - session.Expiration = expiration 227 - 228 - return session, nil 229 - } 230 - 231 - func (s *Service) DeleteOAuthSession(did string) error { 232 - err := s.store.DeleteOauthSession(did) 233 - if err != nil { 234 - return fmt.Errorf("delete oauth session from store: %w", err) 235 - } 236 - return nil 237 - } 238 - 239 - func (s *Service) UpdateOAuthSessionDPopPDSNonce(did, newDPopNonce string) error { 240 - return s.store.UpdateOauthSessionDpopPdsNonce(newDPopNonce, did) 241 - } 242 - 243 - func (s *Service) PublicKey() []byte { 244 - return s.jwks.public 245 - } 246 - 247 - func (s *Service) PdsDpopJwt(method, url string, session Session, privateKey jwk.Key) (string, error) { 248 - return atoauth.PdsDpopJwt(method, url, session.AuthserverIss, session.AccessToken, session.DpopPdsNonce, privateKey) 249 - } 250 - 251 - func (s *Service) makeOAuthRequest(ctx context.Context, did, handle string, dpopPrivateKey jwk.Key) (*atoauth.SendParAuthResponse, *atoauth.OauthAuthorizationMetadata, string, error) { 252 - service, err := s.resolveService(ctx, did) 253 - if err != nil { 254 - return nil, nil, "", err 255 - } 256 - 257 - authserver, err := s.oauthClient.ResolvePdsAuthServer(ctx, service) 258 - if err != nil { 259 - return nil, nil, "", err 260 - } 261 - 262 - meta, err := s.oauthClient.FetchAuthServerMetadata(ctx, authserver) 263 - if err != nil { 264 - return nil, nil, "", err 265 - } 266 - 267 - resp, err := s.oauthClient.SendParAuthRequest(ctx, authserver, meta, handle, scope, dpopPrivateKey) 268 - if err != nil { 269 - return nil, nil, "", err 270 - } 271 - return resp, meta, service, nil 272 - } 273 - 274 - func (s *Service) resolveHandle(handle string) (string, error) { 275 - params := url.Values{ 276 - "handle": []string{handle}, 277 - } 278 - reqUrl := "https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?" + params.Encode() 279 - 280 - resp, err := s.httpClient.Get(reqUrl) 281 - if err != nil { 282 - return "", fmt.Errorf("make http request: %w", err) 283 - } 284 - 285 - defer resp.Body.Close() 286 - 287 - type did struct { 288 - Did string 289 - } 290 - 291 - b, err := io.ReadAll(resp.Body) 292 - if err != nil { 293 - return "", fmt.Errorf("read response body: %w", err) 294 - } 295 - 296 - var resDid did 297 - err = json.Unmarshal(b, &resDid) 298 - if err != nil { 299 - return "", fmt.Errorf("unmarshal response: %w", err) 300 - } 301 - 302 - return resDid.Did, nil 303 - } 304 - 305 - func (s *Service) resolveService(ctx context.Context, did string) (string, error) { 306 - type Identity struct { 307 - Service []struct { 308 - ID string `json:"id"` 309 - Type string `json:"type"` 310 - ServiceEndpoint string `json:"serviceEndpoint"` 311 - } `json:"service"` 312 - } 313 - 314 - var url string 315 - if strings.HasPrefix(did, "did:plc:") { 316 - url = fmt.Sprintf("https://plc.directory/%s", did) 317 - } else if strings.HasPrefix(did, "did:web:") { 318 - url = fmt.Sprintf("https://%s/.well-known/did.json", strings.TrimPrefix(did, "did:web:")) 319 - } else { 320 - return "", fmt.Errorf("did was not a supported did type") 321 - } 322 - 323 - req, err := http.NewRequestWithContext(ctx, "GET", url, nil) 324 - if err != nil { 325 - return "", err 326 - } 327 - 328 - resp, err := s.httpClient.Do(req) 329 - if err != nil { 330 - return "", fmt.Errorf("do http request: %w", err) 331 - } 332 - defer resp.Body.Close() 333 - 334 - if resp.StatusCode != 200 { 335 - return "", fmt.Errorf("could not find identity in plc registry") 336 - } 337 - 338 - b, err := io.ReadAll(resp.Body) 339 - if err != nil { 340 - return "", fmt.Errorf("read response body: %w", err) 341 - } 342 - 343 - var identity Identity 344 - err = json.Unmarshal(b, &identity) 345 - if err != nil { 346 - return "", fmt.Errorf("unmarshal response: %w", err) 347 - } 348 - 349 - var service string 350 - for _, svc := range identity.Service { 351 - if svc.ID == "#atproto_pds" { 352 - service = svc.ServiceEndpoint 353 - } 354 - } 355 - 356 - if service == "" { 357 - return "", fmt.Errorf("could not find atproto_pds service in identity services") 358 - } 359 - 360 - return service, nil 361 - } 362 - 363 - type JWKS struct { 364 - public []byte 365 - private jwk.Key 366 - } 367 - 368 - func getJWKS() (*JWKS, error) { 369 - jwksB64 := os.Getenv("PRIVATEJWKS") 370 - if jwksB64 == "" { 371 - return nil, fmt.Errorf("PRIVATEJWKS env not set") 372 - } 373 - 374 - jwksB, err := base64.StdEncoding.DecodeString(jwksB64) 375 - if err != nil { 376 - return nil, fmt.Errorf("decode jwks env: %w", err) 377 - } 378 - 379 - k, err := oauthhelpers.ParseJWKFromBytes([]byte(jwksB)) 380 - if err != nil { 381 - return nil, fmt.Errorf("parse JWK from bytes: %w", err) 382 - } 383 - 384 - pubkey, err := k.PublicKey() 385 - if err != nil { 386 - return nil, fmt.Errorf("get public key from JWKS: %w", err) 387 - } 388 - 389 - resp := oauthhelpers.CreateJwksResponseObject(pubkey) 390 - b, err := json.Marshal(resp) 391 - if err != nil { 392 - return nil, fmt.Errorf("marshal public JWKS: %w", err) 393 - } 394 - 395 - return &JWKS{ 396 - public: b, 397 - private: k, 398 - }, nil 399 - } 400 - 401 - func createOauthClient(jwks *JWKS, serverBase string, httpClient *http.Client) (*atoauth.Client, error) { 402 - return atoauth.NewClient(atoauth.ClientArgs{ 403 - Http: httpClient, 404 - ClientJwk: jwks.private, 405 - ClientId: fmt.Sprintf("%s/client-metadata.json", serverBase), 406 - RedirectUri: fmt.Sprintf("%s/oauth-callback", serverBase), 407 - }) 408 - }
+27 -38
server.go
··· 15 15 16 16 "github.com/gorilla/sessions" 17 17 18 - "github.com/willdot/statusphere-go/oauth" 18 + "github.com/bluesky-social/indigo/atproto/auth/oauth" 19 19 ) 20 20 21 21 var ErrorNotFound = fmt.Errorf("not found") ··· 38 38 httpserver *http.Server 39 39 sessionStore *sessions.CookieStore 40 40 templates []*template.Template 41 - oauthService *oauth.Service 42 - store Store 43 - httpClient *http.Client 41 + 42 + oauthClient *oauth.ClientApp 43 + store Store 44 + httpClient *http.Client 44 45 } 45 46 46 - func NewServer(host string, port int, store Store, oauthService *oauth.Service, httpClient *http.Client) (*Server, error) { 47 + func NewServer(host string, port int, store Store, oauthClient *oauth.ClientApp, httpClient *http.Client) (*Server, error) { 47 48 sessionStore := sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY"))) 48 49 49 50 homeTemplate, err := template.ParseFiles("./html/home.html") ··· 62 63 63 64 srv := &Server{ 64 65 host: host, 65 - oauthService: oauthService, 66 + oauthClient: oauthClient, 66 67 sessionStore: sessionStore, 67 68 templates: templates, 68 69 store: store, ··· 78 79 mux.HandleFunc("POST /logout", srv.HandleLogOut) 79 80 80 81 mux.HandleFunc("/public/app.css", serveCSS) 81 - mux.HandleFunc("/jwks.json", srv.serveJwks) 82 - mux.HandleFunc("/client-metadata.json", srv.serveClientMetadata) 83 - mux.HandleFunc("/oauth-callback", srv.handleOauthCallback) 82 + mux.HandleFunc("/oauth/jwks.json", srv.serveJwks) 83 + mux.HandleFunc("/oauth/client-metadata.json", srv.serveClientMetadata) 84 + mux.HandleFunc("/oauth/oauth-callback", srv.handleOauthCallback) 84 85 85 86 addr := fmt.Sprintf("0.0.0.0:%d", port) 86 87 srv.httpserver = &http.Server{ ··· 113 114 114 115 func (s *Server) serveJwks(w http.ResponseWriter, _ *http.Request) { 115 116 w.Header().Set("Content-Type", "application/json") 116 - _, _ = w.Write(s.oauthService.PublicKey()) 117 + 118 + public := s.oauthClient.Config.PublicJWKS() 119 + b, err := json.Marshal(public) 120 + if err != nil { 121 + slog.Error("failed to marshal oauth public JWKS", "error", err) 122 + http.Error(w, "marshal public JWKS", http.StatusInternalServerError) 123 + return 124 + } 125 + 126 + _, _ = w.Write(b) 117 127 } 118 128 119 129 //go:embed html/app.css ··· 125 135 } 126 136 127 137 func (s *Server) serveClientMetadata(w http.ResponseWriter, r *http.Request) { 128 - metadata := map[string]any{ 129 - "client_id": fmt.Sprintf("%s/client-metadata.json", s.host), 130 - "client_name": "Bsky-bookmark", 131 - "client_uri": s.host, 132 - "redirect_uris": []string{fmt.Sprintf("%s/oauth-callback", s.host)}, 133 - "grant_types": []string{"authorization_code", "refresh_token"}, 134 - "response_types": []string{"code"}, 135 - "application_type": "web", 136 - "dpop_bound_access_tokens": true, 137 - "jwks_uri": fmt.Sprintf("%s/jwks.json", s.host), 138 - "scope": "atproto transition:generic", 139 - "token_endpoint_auth_method": "private_key_jwt", 140 - "token_endpoint_auth_signing_alg": "ES256", 138 + metadata := s.oauthClient.Config.ClientMetadata() 139 + clientName := "statusphere-go" 140 + metadata.ClientName = &clientName 141 + metadata.ClientURI = &s.host 142 + if s.oauthClient.Config.IsConfidential() { 143 + jwksURI := fmt.Sprintf("%s/oauth/jwks.json", r.Host) 144 + metadata.JWKSURI = &jwksURI 141 145 } 142 146 143 147 b, err := json.Marshal(metadata) ··· 148 152 } 149 153 w.Header().Set("Content-Type", "application/json") 150 154 _, _ = w.Write(b) 151 - } 152 - 153 - func (s *Server) getDidFromSession(r *http.Request) (string, bool) { 154 - session, err := s.sessionStore.Get(r, "oauth-session") 155 - if err != nil { 156 - slog.Error("getting session", "error", err) 157 - return "", false 158 - } 159 - 160 - did, ok := session.Values["did"].(string) 161 - if !ok { 162 - return "", false 163 - } 164 - 165 - return did, true 166 155 } 167 156 168 157 func (s *Server) getUserProfileForDid(did string) (UserProfile, error) {
-119
status.go
··· 1 1 package statusphere 2 2 3 - import ( 4 - "bytes" 5 - "context" 6 - "encoding/json" 7 - "fmt" 8 - "io" 9 - "log/slog" 10 - "net/http" 11 - "time" 12 - 13 - "github.com/willdot/statusphere-go/oauth" 14 - ) 15 - 16 3 type Status struct { 17 4 URI string 18 5 Did string ··· 21 8 IndexedAt int64 22 9 } 23 10 24 - type XRPCError struct { 25 - ErrStr string `json:"error"` 26 - Message string `json:"message"` 27 - } 28 - 29 11 type CreateRecordResp struct { 30 12 URI string `json:"uri"` 31 13 ErrStr string `json:"error"` 32 14 Message string `json:"message"` 33 15 } 34 - 35 - func (s *Server) CreateNewStatus(ctx context.Context, oauthsession oauth.Session, status string, createdAt time.Time) (string, error) { 36 - bodyReq := map[string]any{ 37 - "repo": oauthsession.Did, 38 - "collection": "xyz.statusphere.status", 39 - "record": map[string]any{ 40 - "status": status, 41 - "createdAt": createdAt, 42 - }, 43 - } 44 - 45 - bodyB, err := json.Marshal(bodyReq) 46 - if err != nil { 47 - return "", fmt.Errorf("marshal update message request body: %w", err) 48 - } 49 - 50 - r := bytes.NewReader(bodyB) 51 - url := fmt.Sprintf("%s/xrpc/com.atproto.repo.createRecord", oauthsession.PdsUrl) 52 - request, err := http.NewRequestWithContext(ctx, "POST", url, r) 53 - if err != nil { 54 - return "", fmt.Errorf("create http request: %w", err) 55 - } 56 - 57 - request.Header.Add("Content-Type", "application/json") 58 - request.Header.Add("Accept", "application/json") 59 - request.Header.Set("Authorization", "DPoP "+oauthsession.AccessToken) 60 - 61 - privateKey, err := oauthsession.CreatePrivateKey() 62 - if err != nil { 63 - return "", fmt.Errorf("create private key: %w", err) 64 - } 65 - 66 - // try a maximum of 2 times to make the request. If the first attempt fails because the server returns an unauthorized due to a new use_dpop_nonce being issued, 67 - // then try again. Otherwise just try once. 68 - for range 2 { 69 - dpopJwt, err := s.oauthService.PdsDpopJwt("POST", url, oauthsession, privateKey) 70 - if err != nil { 71 - return "", err 72 - } 73 - 74 - request.Header.Set("DPoP", dpopJwt) 75 - 76 - resp, err := s.httpClient.Do(request) 77 - if err != nil { 78 - return "", fmt.Errorf("do http request: %w", err) 79 - } 80 - defer resp.Body.Close() 81 - 82 - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusBadRequest && resp.StatusCode != http.StatusUnauthorized { 83 - return "", fmt.Errorf("unexpected status code returned: %d", resp.StatusCode) 84 - } 85 - 86 - var result CreateRecordResp 87 - err = decodeResp(resp.Body, &result) 88 - if err != nil { 89 - // just log the error. 90 - // if a HTTP 200 is received then the record has been created and we only use the response URI to make an optimistic write to our DB, so nothing will go wrong here. 91 - // if a HTTP 400 then we can at least log return that it was a bad request. 92 - // if a HTTP 401 we only do something if the error string is use_dpop_nonce 93 - slog.Error("decode response body", "error", err) 94 - } 95 - 96 - slog.Info("resp", "status", resp.StatusCode) 97 - 98 - if resp.StatusCode == http.StatusOK { 99 - return result.URI, nil 100 - } 101 - 102 - if resp.StatusCode == http.StatusBadRequest { 103 - return "", fmt.Errorf("bad request: %s - %s", result.Message, result.ErrStr) 104 - } 105 - 106 - if resp.StatusCode == http.StatusUnauthorized && result.ErrStr == "use_dpop_nonce" { 107 - newNonce := resp.Header.Get("DPoP-Nonce") 108 - oauthsession.DpopPdsNonce = newNonce 109 - err := s.oauthService.UpdateOAuthSessionDPopPDSNonce(oauthsession.Did, newNonce) 110 - if err != nil { 111 - // just log the error because we can still proceed without storing it. 112 - slog.Error("updating oauth session in store with new DPoP PDS nonce", "error", err) 113 - } 114 - continue 115 - } 116 - 117 - return "", fmt.Errorf("received an unauthorized status code and message: %s - %s", result.ErrStr, result.Message) 118 - } 119 - 120 - return "", fmt.Errorf("failed to create status record") 121 - } 122 - 123 - func decodeResp(body io.Reader, result any) error { 124 - resBody, err := io.ReadAll(body) 125 - if err != nil { 126 - return fmt.Errorf("failed to read response: %w", err) 127 - } 128 - 129 - err = json.Unmarshal(resBody, result) 130 - if err != nil { 131 - return fmt.Errorf("failed to unmarshal response: %w", err) 132 - } 133 - return nil 134 - }