From ebc9ad7ac64b3e7eb5f5c8cbc8f581f266dd86d3 Mon Sep 17 00:00:00 2001 From: Will Andrews Date: Thu, 6 Mar 2025 21:07:39 +0000 Subject: [PATCH] Oauth (#7) --- .gitignore | 1 + auth_handlers.go | 175 +++---------------- bookmark_handler.go | 18 +- frontend/base_templ.go | 8 +- frontend/base_templ.txt | 2 - frontend/bookmarks_templ.go | 30 ++-- frontend/bookmarks_templ.txt | 11 -- frontend/home_templ.go | 22 +-- frontend/home_templ.txt | 7 - frontend/login.templ | 10 -- frontend/login_templ.go | 18 +- frontend/login_templ.txt | 5 - frontend/nav_templ.go | 6 +- frontend/nav_templ.txt | 1 - go.mod | 62 ++++--- go.sum | 161 ++++++++++++++--- main.go | 7 +- oauth_handlers.go | 323 +++++++++++++++++++++++++++++++++++ public/styles.css | 49 ------ server.go | 111 ++++++++++-- store/database.go | 5 + store/oauth.go | 87 ++++++++++ 22 files changed, 755 insertions(+), 364 deletions(-) delete mode 100644 frontend/base_templ.txt delete mode 100644 frontend/bookmarks_templ.txt delete mode 100644 frontend/home_templ.txt delete mode 100644 frontend/login_templ.txt delete mode 100644 frontend/nav_templ.txt create mode 100644 oauth_handlers.go create mode 100644 store/oauth.go diff --git a/.gitignore b/.gitignore index 4f8b0c6..4fde198 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ bskyfeedgen /tmp/ .DS_Store +*_templ.txt diff --git a/auth_handlers.go b/auth_handlers.go index 51e76c9..7b9cbf7 100644 --- a/auth_handlers.go +++ b/auth_handlers.go @@ -1,182 +1,45 @@ package main import ( - "bytes" - "encoding/json" "fmt" - "io" "log/slog" "net/http" - "strconv" - "time" - "github.com/golang-jwt/jwt/v5" "github.com/willdot/bskyfeedgen/frontend" ) const ( - bskyBaseURL = "https://bsky.social/xrpc" - jwtCookieName = "JWT" - didCookieName = "DID" + bskyBaseURL = "https://bsky.social/xrpc" ) type loginRequest struct { - Handle string `json:"handle"` - AppPassword string `json:"appPassword"` -} - -type BskyAuth struct { - AccessJwt string `json:"accessJwt"` - Did string `json:"did"` -} - -func (s *Server) HandleLogin(w http.ResponseWriter, r *http.Request) { - b, err := io.ReadAll(r.Body) - if err != nil { - slog.Error("failed to read body", "error", err) - _ = frontend.LoginForm("", "bad request").Render(r.Context(), w) - return - } - - var loginReq loginRequest - err = json.Unmarshal(b, &loginReq) - if err != nil { - slog.Error("failed to unmarshal body", "error", err) - _ = frontend.LoginForm("", "bad request").Render(r.Context(), w) - return - } - url := fmt.Sprintf("%s/com.atproto.server.createsession", bskyBaseURL) - - requestData := map[string]interface{}{ - "identifier": loginReq.Handle, - "password": loginReq.AppPassword, - } - - data, err := json.Marshal(requestData) - if err != nil { - slog.Error("failed marshal POST request to sign into Bsky", "error", err) - _ = frontend.LoginForm(loginReq.Handle, "internal error").Render(r.Context(), w) - return - } - - reader := bytes.NewReader(data) - - req, err := http.NewRequest("POST", url, reader) - if err != nil { - slog.Error("failed to create POST request to sign into Bsky", "error", err) - _ = frontend.LoginForm(loginReq.Handle, "internal error").Render(r.Context(), w) - return - } - - req.Header.Add("Content-Type", "application/json") - - // TODO: create a client somewhere - res, err := http.DefaultClient.Do(req) - if err != nil { - slog.Error("failed to make POST request to sign into Bsky", "error", err) - _ = frontend.LoginForm(loginReq.Handle, "internal error").Render(r.Context(), w) - return - } - - defer res.Body.Close() - - slog.Info("bsky resp", "code", res.StatusCode) - - if res.StatusCode != 200 { - slog.Error("failed to log into bluesky", "status code", res.StatusCode) - _ = frontend.LoginForm(loginReq.Handle, "not authorized").Render(r.Context(), w) - return - } - - resBody, err := io.ReadAll(res.Body) - if err != nil { - slog.Error("failed read response from Bsky login", "error", err) - _ = frontend.LoginForm(loginReq.Handle, "internal error").Render(r.Context(), w) - return - } - - var loginResp BskyAuth - err = json.Unmarshal(resBody, &loginResp) - if err != nil { - slog.Error("failed unmarshal response from Bsky login", "error", err) - _ = frontend.LoginForm(loginReq.Handle, "internal error").Render(r.Context(), w) - return - } - - http.SetCookie(w, &http.Cookie{ - Name: jwtCookieName, - Value: loginResp.AccessJwt, - }) - - http.SetCookie(w, &http.Cookie{ - Name: didCookieName, - Value: loginResp.Did, - }) - - http.Redirect(w, r, "/", http.StatusOK) -} - -func (s *Server) HandleSignOut(w http.ResponseWriter, r *http.Request) { - http.SetCookie(w, &http.Cookie{ - Name: jwtCookieName, - Value: "", - }) - - http.SetCookie(w, &http.Cookie{ - Name: didCookieName, - Value: "", - }) - - _ = frontend.Login("", "").Render(r.Context(), w) + Handle string `json:"handle"` } func (s *Server) authMiddleware(next func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - jwtCookie, err := r.Cookie(jwtCookieName) - if err != nil { - slog.Error("read JWT cookie", "error", err) - _ = frontend.Login("", "").Render(r.Context(), w) - return - } - if jwtCookie == nil { - slog.Error("missing JWT cookie") - _ = frontend.Login("", "").Render(r.Context(), w) - return - } - - didCookie, err := r.Cookie(didCookieName) - if err != nil { - slog.Error("read DID cookie", "error", err) - _ = frontend.Login("", "").Render(r.Context(), w) - return - } - if didCookie == nil { - slog.Error("missing DID cookie") + _, ok := s.getDidFromSession(r) + if !ok { + slog.Warn("did not found in session") _ = frontend.Login("", "").Render(r.Context(), w) return } - claims := jwt.MapClaims{} - _, _, err = jwt.NewParser().ParseUnverified(jwtCookie.Value, &claims) - if err != nil { - slog.Error("parsing JWT", "error", err) - _ = frontend.Login("", "").Render(r.Context(), w) - return - } + next(w, r) + } +} - if expiry, ok := claims["exp"].(string); ok { - expiryInt, err := strconv.Atoi(expiry) - if err != nil { - slog.Error("invalid claims from token", "error", err) - _ = frontend.Login("", "").Render(r.Context(), w) - return - } +func (s *Server) getDidFromSession(r *http.Request) (string, bool) { + session, err := s.sessionStore.Get(r, "oauth-session") + if err != nil { + slog.Error("getting session", "error", err) + return "", false + } - if time.Now().Unix() > int64(expiryInt) { - _ = frontend.Login("", "").Render(r.Context(), w) - return - } - } - next(w, r) + did, ok := session.Values["did"] + if !ok { + return "", false } + + return fmt.Sprintf("%s", did), true } diff --git a/bookmark_handler.go b/bookmark_handler.go index d5c9c90..943c57d 100644 --- a/bookmark_handler.go +++ b/bookmark_handler.go @@ -17,9 +17,9 @@ import ( ) func (s *Server) HandleAddBookmark(w http.ResponseWriter, r *http.Request) { - usersDid, err := getUsersDidFromRequestCookie(r) - if err != nil { - slog.Error("getting users did from request", "error", err) + usersDid, ok := s.getDidFromSession(r) + if !ok { + slog.Warn("did not found in session") _ = frontend.Login("", "").Render(r.Context(), w) return } @@ -115,9 +115,9 @@ func convertPostURIToAtValidURI(input string) (string, error) { func (s *Server) HandleDeleteBookmark(w http.ResponseWriter, r *http.Request) { rKey := r.PathValue("rkey") - usersDid, err := getUsersDidFromRequestCookie(r) - if err != nil { - slog.Error("getting users did from request", "error", err) + usersDid, ok := s.getDidFromSession(r) + if !ok { + slog.Warn("did not found in session") _ = frontend.Login("", "").Render(r.Context(), w) return } @@ -149,9 +149,9 @@ func (s *Server) HandleDeleteBookmark(w http.ResponseWriter, r *http.Request) { } func (s *Server) HandleGetBookmarks(w http.ResponseWriter, r *http.Request) { - usersDid, err := getUsersDidFromRequestCookie(r) - if err != nil { - slog.Error("getting users did from request", "error", err) + usersDid, ok := s.getDidFromSession(r) + if !ok { + slog.Warn("did not found in session") _ = frontend.Login("", "").Render(r.Context(), w) return } diff --git a/frontend/base_templ.go b/frontend/base_templ.go index 5565f2b..5506ebb 100644 --- a/frontend/base_templ.go +++ b/frontend/base_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.2.793 +// templ: version: v0.3.833 package frontend //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -29,7 +29,7 @@ func Base() templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "BSFeeder") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -41,11 +41,11 @@ func Base() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - return templ_7745c5c3_Err + return nil }) } diff --git a/frontend/base_templ.txt b/frontend/base_templ.txt deleted file mode 100644 index 64161e3..0000000 --- a/frontend/base_templ.txt +++ /dev/null @@ -1,2 +0,0 @@ -BSFeeder - diff --git a/frontend/bookmarks_templ.go b/frontend/bookmarks_templ.go index 5449dd0..64ea0f1 100644 --- a/frontend/bookmarks_templ.go +++ b/frontend/bookmarks_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.2.793 +// templ: version: v0.3.833 package frontend //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -38,7 +38,7 @@ func Bookmarks(bookmarks []store.Bookmark) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -48,11 +48,11 @@ func Bookmarks(bookmarks []store.Bookmark) templ.Component { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - return templ_7745c5c3_Err + return nil }) } @@ -77,7 +77,7 @@ func bookmarkRow(bookmark store.Bookmark) templ.Component { templ_7745c5c3_Var2 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "

Author: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -103,7 +103,7 @@ func bookmarkRow(bookmark store.Bookmark) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -125,7 +125,7 @@ func bookmarkRow(bookmark store.Bookmark) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 7) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - return templ_7745c5c3_Err + return nil }) } @@ -180,7 +180,7 @@ func NewBookmarkRow(bookmark store.Bookmark) templ.Component { templ_7745c5c3_Var9 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 10) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -188,11 +188,11 @@ func NewBookmarkRow(bookmark store.Bookmark) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 11) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - return templ_7745c5c3_Err + return nil }) } diff --git a/frontend/bookmarks_templ.txt b/frontend/bookmarks_templ.txt deleted file mode 100644 index 2af20a9..0000000 --- a/frontend/bookmarks_templ.txt +++ /dev/null @@ -1,11 +0,0 @@ -
-
-

Author: -

- - - diff --git a/frontend/home_templ.go b/frontend/home_templ.go index 48d8ffd..9e493b1 100644 --- a/frontend/home_templ.go +++ b/frontend/home_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.2.793 +// templ: version: v0.3.833 package frontend //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -33,7 +33,7 @@ func Home() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - return templ_7745c5c3_Err + return nil }) } @@ -62,7 +62,7 @@ func Account() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - return templ_7745c5c3_Err + return nil }) } @@ -91,7 +91,7 @@ func Something(name, email string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Your username is

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -104,7 +104,7 @@ func Something(name, email string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

Your email is

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -117,7 +117,7 @@ func Something(name, email string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "

Your username is

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -130,7 +130,7 @@ func Something(name, email string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

Your email is

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -143,7 +143,7 @@ func Something(name, email string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

Your username is

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -156,7 +156,7 @@ func Something(name, email string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 6) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

Your email is

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -169,11 +169,11 @@ func Something(name, email string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 7) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - return templ_7745c5c3_Err + return nil }) } diff --git a/frontend/home_templ.txt b/frontend/home_templ.txt deleted file mode 100644 index 757471c..0000000 --- a/frontend/home_templ.txt +++ /dev/null @@ -1,7 +0,0 @@ -

Your username is

-

Your email is

-

Your username is

-

Your email is

-

Your username is

-

Your email is

-

diff --git a/frontend/login.templ b/frontend/login.templ index 95e42c0..cf0adfb 100644 --- a/frontend/login.templ +++ b/frontend/login.templ @@ -18,16 +18,6 @@ templ LoginForm(handle, errorMsg string) { -
-
- -
-
- -
-
diff --git a/frontend/login_templ.go b/frontend/login_templ.go index 99cffa3..5906544 100644 --- a/frontend/login_templ.go +++ b/frontend/login_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.2.793 +// templ: version: v0.3.833 package frontend //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -37,7 +37,7 @@ func Login(handle, errorMsg string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - return templ_7745c5c3_Err + return nil }) } @@ -62,7 +62,7 @@ func LoginForm(handle, errorMsg string) templ.Component { templ_7745c5c3_Var2 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if errorMsg != "" { - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - return templ_7745c5c3_Err + return nil }) } diff --git a/frontend/login_templ.txt b/frontend/login_templ.txt deleted file mode 100644 index bd638b7..0000000 --- a/frontend/login_templ.txt +++ /dev/null @@ -1,5 +0,0 @@ -
-
-
diff --git a/frontend/nav_templ.go b/frontend/nav_templ.go index dc7dc39..f222948 100644 --- a/frontend/nav_templ.go +++ b/frontend/nav_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.2.793 +// templ: version: v0.3.833 package frontend //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -31,11 +31,11 @@ func Nav() templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - return templ_7745c5c3_Err + return nil }) } diff --git a/frontend/nav_templ.txt b/frontend/nav_templ.txt deleted file mode 100644 index 2b25b44..0000000 --- a/frontend/nav_templ.txt +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/go.mod b/go.mod index f83c161..e24c385 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,21 @@ module github.com/willdot/bskyfeedgen -go 1.23.3 +go 1.24.0 -toolchain go1.23.4 +toolchain go1.24.1 require ( - github.com/a-h/templ v0.2.793 + github.com/a-h/templ v0.3.833 github.com/avast/retry-go/v4 v4.6.0 - github.com/bluesky-social/indigo v0.0.0-20241031232035-1a73c3fb6841 + github.com/bluesky-social/indigo v0.0.0-20250305203105-a2e0aaff387e github.com/bluesky-social/jetstream v0.0.0-20241031234625-0ab10bd041fe github.com/bugsnag/bugsnag-go/v2 v2.5.1 github.com/glebarez/go-sqlite v1.22.0 github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/gorilla/sessions v1.4.0 + github.com/haileyok/atproto-oauth-golang v0.0.2 github.com/joho/godotenv v1.5.1 - github.com/stretchr/testify v1.9.0 + github.com/lestrrat-go/jwx/v2 v2.1.4 ) require ( @@ -21,35 +23,41 @@ require ( github.com/bugsnag/panicwrap v1.3.4 // indirect github.com/carlmjohnson/versioninfo v0.22.5 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/websocket v1.5.1 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-retryablehttp v0.7.5 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/go-block-format v0.2.0 // indirect - github.com/ipfs/go-cid v0.4.1 // indirect - github.com/ipfs/go-datastore v0.6.0 // indirect + github.com/ipfs/go-cid v0.5.0 // indirect + github.com/ipfs/go-datastore v0.8.2 // indirect github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect github.com/ipfs/go-ipfs-util v0.0.3 // indirect - github.com/ipfs/go-ipld-cbor v0.1.0 // indirect + github.com/ipfs/go-ipld-cbor v0.2.0 // indirect github.com/ipfs/go-ipld-format v0.6.0 // indirect github.com/ipfs/go-log v1.0.5 // indirect github.com/ipfs/go-log/v2 v2.5.1 // indirect - github.com/ipfs/go-metrics-interface v0.0.1 // indirect + github.com/ipfs/go-metrics-interface v0.3.0 // indirect github.com/jbenet/goprocess v0.1.4 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/klauspost/compress v1.17.9 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.6 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mr-tron/base58 v1.2.0 // indirect @@ -60,32 +68,32 @@ require ( github.com/multiformats/go-varint v0.0.7 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.54.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/segmentio/asm v1.2.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/whyrusleeping/cbor-gen v0.1.3-0.20240904181319-8dc02b38228c // indirect + github.com/whyrusleeping/cbor-gen v0.3.1 // indirect gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect - go.opentelemetry.io/otel v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.21.0 // indirect - go.opentelemetry.io/otel/trace v1.21.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.26.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sys v0.23.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/time v0.8.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - lukechampine.com/blake3 v1.2.1 // indirect + lukechampine.com/blake3 v1.4.0 // indirect modernc.org/libc v1.37.6 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.7.2 // indirect diff --git a/go.sum b/go.sum index 3a73e42..b8587af 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/a-h/templ v0.2.793 h1:Io+/ocnfGWYO4VHdR0zBbf39PQlnzVCVVD+wEEs6/qY= github.com/a-h/templ v0.2.793/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w= +github.com/a-h/templ v0.3.833 h1:L/KOk/0VvVTBegtE0fp2RJQiBm7/52Zxv5fqlEHiQUU= +github.com/a-h/templ v0.3.833/go.mod h1:cAu4AiZhtJfBjMY0HASlyzvkrtjnHWPeEsyGK2YYmfk= github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -8,8 +10,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow= github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q= -github.com/bluesky-social/indigo v0.0.0-20241031232035-1a73c3fb6841 h1:HCj4iBoAV59Fn1vsEhbeEMOExR4vOrUgz+sxUoV+F6s= -github.com/bluesky-social/indigo v0.0.0-20241031232035-1a73c3fb6841/go.mod h1:Zx9nSWgd/FxMenkJW07VKnzspxpHBdPrPmS+Fspl2I0= +github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188 h1:1sQaG37xk08/rpmdhrmMkfQWF9kZbnfHm9Zav3bbSMk= +github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188/go.mod h1:NVBwZvbBSa93kfyweAmKwOLYawdVHdwZ9s+GZtBBVLA= +github.com/bluesky-social/indigo v0.0.0-20250305203105-a2e0aaff387e h1:cLBQmPW9D6F7F3FRoFeu5yyHzWo77nSun3plvHLM7yM= +github.com/bluesky-social/indigo v0.0.0-20250305203105-a2e0aaff387e/go.mod h1:NVBwZvbBSa93kfyweAmKwOLYawdVHdwZ9s+GZtBBVLA= github.com/bluesky-social/jetstream v0.0.0-20241031234625-0ab10bd041fe h1:jduuyDfsiwWrPiN7psqDehepl68uxQ4UYCIgoqb1D4o= github.com/bluesky-social/jetstream v0.0.0-20241031234625-0ab10bd041fe/go.mod h1:WiYEeyJSdUwqoaZ71KJSpTblemUCpwJfh5oVXplK6T4= github.com/bugsnag/bugsnag-go/v2 v2.5.1 h1:cGsEJHcis1zfQ4KoFaBPIT4N1TYqVNRALKr2wMRZ4hs= @@ -22,8 +26,14 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -31,19 +41,25 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -51,14 +67,23 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= +github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/haileyok/atproto-oauth-golang v0.0.2 h1:61KPkLB615LQXR2f5x1v3sf6vPe6dOXqNpTYCgZ0Fz8= +github.com/haileyok/atproto-oauth-golang v0.0.2/go.mod h1:jcZ4GCjo5I5RuE/RsAXg1/b6udw7R4W+2rb/cGyTDK8= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -69,8 +94,12 @@ github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNi github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= +github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg= +github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk= github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= +github.com/ipfs/go-datastore v0.8.2 h1:Jy3wjqQR6sg/LhyY0NIePZC3Vux19nLtg7dx0TVqr6U= +github.com/ipfs/go-datastore v0.8.2/go.mod h1:W+pI1NsUsz3tcsAACMtfC+IZdnQTnC/7VfPoJBQuts0= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ= @@ -81,6 +110,8 @@ github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCm github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= github.com/ipfs/go-ipld-cbor v0.1.0 h1:dx0nS0kILVivGhfWuB6dUpMa/LAwElHPw1yOGYopoYs= github.com/ipfs/go-ipld-cbor v0.1.0/go.mod h1:U2aYlmVrJr2wsUBU67K4KgepApSZddGRDWBYR0H4sCk= +github.com/ipfs/go-ipld-cbor v0.2.0 h1:VHIW3HVIjcMd8m4ZLZbrYpwjzqlVUfjLM7oK4T5/YF0= +github.com/ipfs/go-ipld-cbor v0.2.0/go.mod h1:Cp8T7w1NKcu4AQJLqK0tWpd1nkgTxEVB5C6kVpLW6/0= github.com/ipfs/go-ipld-format v0.6.0 h1:VEJlA2kQ3LqFSIm5Vu6eIlSxD/Ze90xtc4Meten1F5U= github.com/ipfs/go-ipld-format v0.6.0/go.mod h1:g4QVMTn3marU3qXchwjpKPKgJv+zF+OlaKMyhJ4LHPg= github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= @@ -90,6 +121,8 @@ github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= +github.com/ipfs/go-metrics-interface v0.3.0 h1:YwG7/Cy4R94mYDUuwsBfeziJCVm9pBMJ6q/JR9V40TU= +github.com/ipfs/go-metrics-interface v0.3.0/go.mod h1:OxxQjZDGocXVdyTPocns6cOLwHieqej/jos7H4POwoY= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= @@ -105,6 +138,8 @@ github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2 github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -112,6 +147,24 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= +github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= +github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluSDWeA= +github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ= +github.com/lestrrat-go/jwx/v2 v2.1.4 h1:uBCMmJX8oRZStmKuMMOFb0Yh9xmEMgNJLgjuKKt4/qc= +github.com/lestrrat-go/jwx/v2 v2.1.4/go.mod h1:nWRbDFR1ALG2Z6GJbBXzfQaYyvn751KuuyySN2yR6is= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -134,8 +187,9 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= @@ -149,9 +203,11 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= @@ -160,32 +216,51 @@ github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3 github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= -github.com/whyrusleeping/cbor-gen v0.1.3-0.20240904181319-8dc02b38228c h1:UsxJNcLPfyLyVaA4iusIrsLAqJn/xh36Qgb8emqtXzk= -github.com/whyrusleeping/cbor-gen v0.1.3-0.20240904181319-8dc02b38228c/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= +github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4= +github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= +github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0= +github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -206,41 +281,69 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -251,12 +354,16 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -273,6 +380,8 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w= +lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0= modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= diff --git a/main.go b/main.go index 8750a23..78525a0 100644 --- a/main.go +++ b/main.go @@ -83,7 +83,12 @@ func main() { go consumeLoop(ctx, store) } - server := NewServer(443, feeder, feedHost, feedDidBase, store) + server, err := NewServer(443, feeder, feedHost, feedDidBase, store) + if err != nil { + slog.Error("create new server", "error", err) + _ = bugsnag.Notify(err) + return + } go func() { <-signals cancel() diff --git a/oauth_handlers.go b/oauth_handlers.go new file mode 100644 index 0000000..f362172 --- /dev/null +++ b/oauth_handlers.go @@ -0,0 +1,323 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "io" + "log/slog" + "net/http" + "net/url" + "strings" + + "github.com/gorilla/sessions" + oauth "github.com/haileyok/atproto-oauth-golang" + oauthhelpers "github.com/haileyok/atproto-oauth-golang/helpers" + "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/willdot/bskyfeedgen/frontend" + "github.com/willdot/bskyfeedgen/store" +) + +const ( + scope = "atproto transition:generic" +) + +func (s *Server) serverJwks(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(s.jwks.public) +} + +func (s *Server) serveClientMetadata(w http.ResponseWriter, r *http.Request) { + metadata := map[string]any{ + "client_id": fmt.Sprintf("https://%s/client-metadata.json", s.feedHost), + "client_name": "BS Feeder", + "client_uri": fmt.Sprintf("https://%s", s.feedHost), + "redirect_uris": []string{fmt.Sprintf("https://%s/oauth-callback", s.feedHost)}, + "grant_types": []string{"authorization_code", "refresh_token"}, + "response_types": []string{"code"}, + "application_type": "web", + "dpop_bound_access_tokens": true, + "jwks_uri": fmt.Sprintf("https://%s/jwks.json", s.feedHost), + "scope": "atproto transition:generic", + "token_endpoint_auth_method": "private_key_jwt", + "token_endpoint_auth_signing_alg": "ES256", + } + + b, err := json.Marshal(metadata) + if err != nil { + slog.Error("failed to marshal client metadata", "error", err) + http.Error(w, "marshal response", http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(b) +} + +func (s *Server) HandleLogin(w http.ResponseWriter, r *http.Request) { + b, err := io.ReadAll(r.Body) + if err != nil { + slog.Error("failed to read body", "error", err) + _ = frontend.Login("", "bad request").Render(r.Context(), w) + return + } + + var loginReq loginRequest + err = json.Unmarshal(b, &loginReq) + if err != nil { + slog.Error("failed to unmarshal body", "error", err) + _ = frontend.Login("", "bad request").Render(r.Context(), w) + return + } + + usersDID, err := resolveHandle(loginReq.Handle) + if err != nil { + slog.Error("resolve users handle", "error", err) + _ = frontend.Login("", "bad request").Render(r.Context(), w) + return + } + + dpopPrivateKey, err := oauthhelpers.GenerateKey(nil) + if err != nil { + slog.Error("generate key", "error", err) + _ = frontend.Login("", "internal server errror").Render(r.Context(), w) + return + } + + parResp, meta, err := s.parseLoginRequest(r.Context(), usersDID, loginReq.Handle, dpopPrivateKey) + if err != nil { + slog.Error("handle login request", "error", err) + _ = frontend.Login("", "internal server errror").Render(r.Context(), w) + return + } + + dpopPrivateKeyJson, err := json.Marshal(dpopPrivateKey) + if err != nil { + slog.Error("marshal key", "error", err) + _ = frontend.Login("", "internal server errror").Render(r.Context(), w) + return + } + + oauthRequst := store.OauthRequest{ + AuthserverIss: meta.Issuer, + State: parResp.State, + Did: usersDID, + PkceVerifier: parResp.PkceVerifier, + DpopAuthserverNonce: parResp.DpopAuthserverNonce, + DpopPrivateJwk: string(dpopPrivateKeyJson), + } + err = s.oauthRequestStore.CreateOauthRequest(oauthRequst) + if err != nil { + // TODO: catch already exists + slog.Error("create oauth request in store", "error", err) + _ = frontend.Login("", "internal server errror").Render(r.Context(), w) + return + } + + u, _ := url.Parse(meta.AuthorizationEndpoint) + u.RawQuery = fmt.Sprintf("client_id=%s&request_uri=%s", url.QueryEscape(fmt.Sprintf("https://%s/client-metadata.json", s.feedHost)), parResp.RequestUri) + + // ignore error here as it only returns an error for decoding an existing session but it will always return a session anyway which + // is what we want + session, _ := s.sessionStore.Get(r, "oauth-session") + session.Values = map[interface{}]interface{}{} + + session.Options = &sessions.Options{ + Path: "/", + MaxAge: 300, // save for five minutes + HttpOnly: true, + } + + session.Values["oauth_state"] = parResp.State + session.Values["oauth_did"] = usersDID + + err = session.Save(r, w) + if err != nil { + slog.Error("save session", "error", err) + _ = frontend.Login("", "internal server errror").Render(r.Context(), w) + return + } + + w.Header().Add("HX-Redirect", u.String()) + http.Redirect(w, r, u.String(), http.StatusOK) +} + +func (s *Server) parseLoginRequest(ctx context.Context, did, handle string, dpopPrivateKey jwk.Key) (*oauth.SendParAuthResponse, *oauth.OauthAuthorizationMetadata, error) { + service, err := resolveService(ctx, did) + if err != nil { + return nil, nil, err + } + + authserver, err := s.oauthClient.ResolvePdsAuthServer(ctx, service) + if err != nil { + return nil, nil, err + } + + meta, err := s.oauthClient.FetchAuthServerMetadata(ctx, authserver) + if err != nil { + return nil, nil, err + } + + resp, err := s.oauthClient.SendParAuthRequest(ctx, authserver, meta, handle, scope, dpopPrivateKey) + if err != nil { + return nil, nil, err + } + return resp, meta, nil +} + +func (s *Server) handleOauthCallback(w http.ResponseWriter, r *http.Request) { + resState := r.FormValue("state") + resIss := r.FormValue("iss") + resCode := r.FormValue("code") + + session, err := s.sessionStore.Get(r, "oauth-session") + if err != nil { + slog.Error("getting session", "error", err) + _ = frontend.Login("", "internal server error").Render(r.Context(), w) + return + } + + sessionState := session.Values["oauth_state"] + + if resState == "" || resIss == "" || resCode == "" { + slog.Error("request missing needed parameters") + _ = frontend.Login("", "internal server error").Render(r.Context(), w) + return + } + + if resState != sessionState { + slog.Error("session state does not match response state") + _ = frontend.Login("", "internal server error").Render(r.Context(), w) + return + } + + oauthRequest, err := s.oauthRequestStore.GetOauthRequest(fmt.Sprintf("%s", sessionState)) + if err != nil { + slog.Error("get oauth request from store", "error", err) + _ = frontend.Login("", "internal server errror").Render(r.Context(), w) + return + } + + err = s.oauthRequestStore.DeleteOauthRequest(fmt.Sprintf("%s", sessionState)) + if err != nil { + slog.Error("delete oauth request from store", "error", err) + _ = frontend.Login("", "internal server errror").Render(r.Context(), w) + return + } + + jwk, err := oauthhelpers.ParseJWKFromBytes([]byte(oauthRequest.DpopPrivateJwk)) + if err != nil { + slog.Error("parse JWK", "error", err) + _ = frontend.Login("", "internal server errror").Render(r.Context(), w) + return + } + + initialTokenResp, err := s.oauthClient.InitialTokenRequest(r.Context(), resCode, resIss, oauthRequest.PkceVerifier, oauthRequest.DpopAuthserverNonce, jwk) + if err != nil { + slog.Error("getting token from request", "error", err) + _ = frontend.Login("", "internal server error").Render(r.Context(), w) + return + } + + if initialTokenResp.Scope != scope { + slog.Error("did not receive correct scopes from token request") + _ = frontend.Login("", "internal server errror").Render(r.Context(), w) + return + } + + session.Options = &sessions.Options{ + Path: "/", + MaxAge: 86400 * 7, + HttpOnly: true, + } + + // make sure the session is empty + session.Values = map[interface{}]interface{}{} + session.Values["did"] = oauthRequest.Did + + err = session.Save(r, w) + if err != nil { + slog.Error("save session", "error", err) + _ = frontend.Login("", "internal server errror").Render(r.Context(), w) + return + } + + // TODO: work out how the hell HTMX redirects really work + s.HandleGetBookmarks(w, r) +} + +func (s *Server) HandleSignOut(w http.ResponseWriter, r *http.Request) { + session, err := s.sessionStore.Get(r, "oauth-session") + if err != nil { + slog.Error("getting session", "error", err) + _ = frontend.Login("", "internal server error").Render(r.Context(), w) + return + } + session.Values = map[interface{}]interface{}{} + session.Options = &sessions.Options{ + Path: "/", + MaxAge: -1, + HttpOnly: true, + } + + err = session.Save(r, w) + if err != nil { + slog.Error("save session", "error", err) + _ = frontend.Login("", "internal server errror").Render(r.Context(), w) + return + } + + _ = frontend.Login("", "").Render(r.Context(), w) +} + +func resolveService(ctx context.Context, did string) (string, error) { + type Identity struct { + Service []struct { + ID string `json:"id"` + Type string `json:"type"` + ServiceEndpoint string `json:"serviceEndpoint"` + } `json:"service"` + } + + var ustr string + if strings.HasPrefix(did, "did:plc:") { + ustr = fmt.Sprintf("https://plc.directory/%s", did) + } else if strings.HasPrefix(did, "did:web:") { + ustr = fmt.Sprintf("https://%s/.well-known/did.json", strings.TrimPrefix(did, "did:web:")) + } else { + return "", fmt.Errorf("did was not a supported did type") + } + + req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil) + if err != nil { + return "", err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + io.Copy(io.Discard, resp.Body) + return "", fmt.Errorf("could not find identity in plc registry") + } + + var identity Identity + if err := json.NewDecoder(resp.Body).Decode(&identity); err != nil { + return "", err + } + + var service string + for _, svc := range identity.Service { + if svc.ID == "#atproto_pds" { + service = svc.ServiceEndpoint + } + } + + if service == "" { + return "", fmt.Errorf("could not find atproto_pds service in identity services") + } + + return service, nil +} diff --git a/public/styles.css b/public/styles.css index 3e452b0..c8a6d9b 100644 --- a/public/styles.css +++ b/public/styles.css @@ -611,10 +611,6 @@ video { height: 100vh; } -.h-3 { - height: 0.75rem; -} - .w-3\/12 { width: 25%; } @@ -627,10 +623,6 @@ video { width: 100%; } -.w-1\/3 { - width: 33.333333%; -} - .max-w-md { max-width: 28rem; } @@ -649,10 +641,6 @@ video { appearance: none; } -.flex-col { - flex-direction: column; -} - .items-center { align-items: center; } @@ -690,10 +678,6 @@ video { overflow: hidden; } -.overflow-x-auto { - overflow-x: auto; -} - .whitespace-nowrap { white-space: nowrap; } @@ -706,11 +690,6 @@ video { border-radius: 0.5rem; } -.rounded-b { - border-bottom-right-radius: 0.25rem; - border-bottom-left-radius: 0.25rem; -} - .border { border-width: 1px; } @@ -719,20 +698,11 @@ video { border-width: 2px; } -.border-t-0 { - border-top-width: 0px; -} - .border-gray-200 { --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity, 1)); } -.border-red-400 { - --tw-border-opacity: 1; - border-color: rgb(248 113 113 / var(--tw-border-opacity, 1)); -} - .bg-blue-500 { --tw-bg-opacity: 1; background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1)); @@ -748,11 +718,6 @@ video { background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1)); } -.bg-red-100 { - --tw-bg-opacity: 1; - background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1)); -} - .bg-white { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); @@ -797,11 +762,6 @@ video { padding-bottom: 0.5rem; } -.py-3 { - padding-top: 0.75rem; - padding-bottom: 0.75rem; -} - .py-6 { padding-top: 1.5rem; padding-bottom: 1.5rem; @@ -819,10 +779,6 @@ video { padding-top: 1.5rem; } -.pt-5 { - padding-top: 1.25rem; -} - .text-center { text-align: center; } @@ -892,11 +848,6 @@ video { color: rgb(239 68 68 / var(--tw-text-opacity, 1)); } -.text-red-700 { - --tw-text-opacity: 1; - color: rgb(185 28 28 / var(--tw-text-opacity, 1)); -} - .text-white { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity, 1)); diff --git a/server.go b/server.go index f0b6673..c7b2353 100644 --- a/server.go +++ b/server.go @@ -3,11 +3,18 @@ package main import ( "context" _ "embed" + "encoding/base64" + "encoding/json" "fmt" "log/slog" "net/http" + "os" "github.com/bluesky-social/indigo/xrpc" + "github.com/gorilla/sessions" + oauth "github.com/haileyok/atproto-oauth-golang" + oauthhelpers "github.com/haileyok/atproto-oauth-golang/helpers" + "github.com/lestrrat-go/jwx/v2/jwk" "github.com/willdot/bskyfeedgen/store" ) @@ -15,6 +22,11 @@ type Feeder interface { GetFeed(ctx context.Context, userDID, feed, cursor string, limit int) (FeedReponse, error) } +type Store interface { + BookmarkStore + OauthRequestStore +} + type BookmarkStore interface { CreateBookmark(postRKey, postURI, postATURI, authorDID, authorHandle, userDID, content string) error GetBookmarksForUser(userDID string) ([]store.Bookmark, error) @@ -23,21 +35,52 @@ type BookmarkStore interface { DeleteFeedPostsForBookmarkedPostURIandUserDID(subscribedPostURI, userDID string) error } +type OauthRequestStore interface { + CreateOauthRequest(request store.OauthRequest) error + GetOauthRequest(state string) (store.OauthRequest, error) + DeleteOauthRequest(state string) error +} + type Server struct { - httpsrv *http.Server - feeder Feeder - feedHost string - feedDidBase string - bookmarkStore BookmarkStore - xrpcClient *xrpc.Client + httpsrv *http.Server + feeder Feeder + feedHost string + feedDidBase string + bookmarkStore BookmarkStore + oauthRequestStore OauthRequestStore + xrpcClient *xrpc.Client + jwks *JWKS + oauthClient *oauth.Client + sessionStore *sessions.CookieStore } -func NewServer(port int, feeder Feeder, feedHost, feedDidBase string, bookmarkStore BookmarkStore) *Server { +type JWKS struct { + public []byte + private jwk.Key +} + +func NewServer(port int, feeder Feeder, feedHost, feedDidBase string, store Store) (*Server, error) { + jwks, err := getJWKS() + if err != nil { + return nil, fmt.Errorf("create public JWKS: %w", err) + } + + oauthClient, err := createOauthClient(jwks, fmt.Sprintf("https://%s", feedHost)) + if err != nil { + return nil, fmt.Errorf("create oauth client: %w", err) + } + + sessionStore := sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY"))) + srv := &Server{ - feeder: feeder, - feedHost: feedHost, - feedDidBase: feedDidBase, - bookmarkStore: bookmarkStore, + feeder: feeder, + feedHost: feedHost, + feedDidBase: feedDidBase, + bookmarkStore: store, + oauthRequestStore: store, + jwks: jwks, + oauthClient: oauthClient, + sessionStore: sessionStore, } mux := http.NewServeMux() @@ -45,6 +88,9 @@ func NewServer(port int, feeder Feeder, feedHost, feedDidBase string, bookmarkSt mux.HandleFunc("/xrpc/app.bsky.feed.getFeedSkeleton", srv.HandleGetFeedSkeleton) mux.HandleFunc("/xrpc/app.bsky.feed.describeFeedGenerator", srv.HandleDescribeFeedGenerator) mux.HandleFunc("/.well-known/did.json", srv.HandleWellKnown) + mux.HandleFunc("/client-metadata.json", srv.serveClientMetadata) + mux.HandleFunc("/jwks.json", srv.serverJwks) + mux.HandleFunc("/oauth-callback", srv.handleOauthCallback) mux.HandleFunc("/", srv.authMiddleware(srv.HandleGetBookmarks)) mux.HandleFunc("/login", srv.HandleLogin) @@ -65,7 +111,7 @@ func NewServer(port int, feeder Feeder, feedHost, feedDidBase string, bookmarkSt Host: "https://public.api.bsky.app", } - return srv + return srv, nil } func (s *Server) Run() { @@ -87,14 +133,43 @@ func serveCSS(w http.ResponseWriter, r *http.Request) { _, _ = w.Write(cssFile) } -func getUsersDidFromRequestCookie(r *http.Request) (string, error) { - didCookie, err := r.Cookie(didCookieName) +func getJWKS() (*JWKS, error) { + jwksB64 := os.Getenv("PRIVATEJWKS") + if jwksB64 == "" { + return nil, fmt.Errorf("PRIVATEJWKS env not set") + } + + jwksB, err := base64.StdEncoding.DecodeString(jwksB64) + if err != nil { + return nil, fmt.Errorf("decode jwks env: %w", err) + } + + k, err := oauthhelpers.ParseJWKFromBytes([]byte(jwksB)) + if err != nil { + return nil, fmt.Errorf("parse JWK from bytes: %w", err) + } + + pubkey, err := k.PublicKey() if err != nil { - return "", err + return nil, fmt.Errorf("get public key from JWKS: %w", err) } - if didCookie == nil { - return "", fmt.Errorf("missing did cookie") + + resp := oauthhelpers.CreateJwksResponseObject(pubkey) + b, err := json.Marshal(resp) + if err != nil { + return nil, fmt.Errorf("marshal public JWKS: %w", err) } - return didCookie.Value, nil + return &JWKS{ + public: b, + private: k, + }, nil +} + +func createOauthClient(jwks *JWKS, serverBase string) (*oauth.Client, error) { + return oauth.NewClient(oauth.ClientArgs{ + ClientJwk: jwks.private, + ClientId: fmt.Sprintf("%s/client-metadata.json", serverBase), + RedirectUri: fmt.Sprintf("%s/oauth-callback", serverBase), + }) } diff --git a/store/database.go b/store/database.go index d5110ed..0dd733e 100644 --- a/store/database.go +++ b/store/database.go @@ -42,6 +42,11 @@ func New(dbPath string) (*Store, error) { return nil, fmt.Errorf("creating bookmarks table: %w", err) } + err = createOauthRequestsTable(db) + if err != nil { + return nil, fmt.Errorf("creating oauth requests table: %w", err) + } + return &Store{db: db}, nil } diff --git a/store/oauth.go b/store/oauth.go new file mode 100644 index 0000000..88a0b8b --- /dev/null +++ b/store/oauth.go @@ -0,0 +1,87 @@ +package store + +import ( + "database/sql" + "errors" + "fmt" + "log/slog" +) + +var ErrOauthRequestAlreadyExists = errors.New("oauth request already exists") + +func createOauthRequestsTable(db *sql.DB) error { + createOauthRequestsTableSQL := `CREATE TABLE IF NOT EXISTS oauthrequests ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "authserverIss" TEXT, + "state" TEXT, + "did" TEXT, + "pkceVerifier" TEXT, + "dpopAuthserverNonce" TEXT, + "dpopPrivateJwk" TEXT, + UNIQUE(did,state) + );` + + slog.Info("Create oauthrequests table...") + statement, err := db.Prepare(createOauthRequestsTableSQL) + if err != nil { + return fmt.Errorf("prepare DB statement to create oauthrequests table: %w", err) + } + _, err = statement.Exec() + if err != nil { + return fmt.Errorf("exec sql statement to create oauthrequests table: %w", err) + } + slog.Info("oauthrequests table created") + + return nil +} + +type OauthRequest struct { + ID uint + AuthserverIss string + State string + Did string + PkceVerifier string + DpopAuthserverNonce string + DpopPrivateJwk string +} + +func (s *Store) CreateOauthRequest(request OauthRequest) error { + sql := `INSERT INTO oauthrequests (authserverIss, state, did, pkceVerifier, dpopAuthServerNonce, dpopPrivateJwk) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(did,state) DO NOTHING;` + res, err := s.db.Exec(sql, request.AuthserverIss, request.State, request.Did, request.PkceVerifier, request.DpopAuthserverNonce, request.DpopPrivateJwk) + if err != nil { + return fmt.Errorf("exec insert oauth request: %w", err) + } + + if x, _ := res.RowsAffected(); x == 0 { + return ErrOauthRequestAlreadyExists + } + return nil +} + +func (s *Store) GetOauthRequest(state string) (OauthRequest, error) { + var oauthRequest OauthRequest + sql := "SELECT authserverIss, state, did, pkceVerifier, dpopAuthServerNonce, dpopPrivateJwk FROM oauthrequests WHERE state = ?;" + rows, err := s.db.Query(sql, state) + if err != nil { + return oauthRequest, fmt.Errorf("run query to get oauth request: %w", err) + } + defer rows.Close() + + for rows.Next() { + if err := rows.Scan(&oauthRequest.AuthserverIss, &oauthRequest.State, &oauthRequest.Did, &oauthRequest.PkceVerifier, &oauthRequest.DpopAuthserverNonce, &oauthRequest.DpopPrivateJwk); err != nil { + return oauthRequest, fmt.Errorf("scan row: %w", err) + } + + return oauthRequest, nil + } + return oauthRequest, fmt.Errorf("not found") +} + +func (s *Store) DeleteOauthRequest(state string) error { + sql := "DELETE FROM oauthrequests WHERE state = ?;" + _, err := s.db.Exec(sql, state) + if err != nil { + return fmt.Errorf("exec delete oauth request: %w", err) + } + return nil +} -- 2.51.2