this repo has no description
0
fork

Configure Feed

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

add rudimentary xrpc client

Hailey 497accf5 6174f06f

+796 -110
+2 -1
.env.example
··· 1 1 OAUTH_TEST_SERVER_ADDR=":7070" 2 2 OAUTH_TEST_SERVER_URL_ROOT="http://127.0.0.1:7070" 3 - OAUTH_TEST_PDS_URL="https://pds.haileyok.com" 3 + OAUTH_TEST_PDS_URL="" 4 + OAUTH_TEST_SESSION_SECRET=""
+12
cmd/client_test/html/index.html
··· 1 1 <!doctype html> 2 2 <html> 3 3 <p>welcome to atproto oauth golang tester!</p> 4 + {{ if .Did }} 5 + <p>logged in as {{ .Did }}</p> 6 + {{ end }} 4 7 <ul> 8 + {{ if .Did }} 9 + <li> 10 + <a href="/make-post">Make a Post</a> 11 + </li> 12 + <li> 13 + <a href="/logout">Logout</a> 14 + </li> 15 + {{ else }} 5 16 <li> 6 17 <a href="/login">Login</a> 7 18 </li> 19 + {{ end }} 8 20 </ul> 9 21 </html>
+5
cmd/client_test/html/make-post.html
··· 1 + <!doctype html> 2 + <html> 3 + <p>Successfully created a post!</p> 4 + <p><a href="/">Go to home</a></p> 5 + </html>
+277 -80
cmd/client_test/main.go
··· 4 4 "context" 5 5 "encoding/json" 6 6 "fmt" 7 + "html/template" 7 8 "io" 8 9 "log/slog" 9 10 "net" ··· 12 13 "os" 13 14 "strings" 14 15 16 + "github.com/bluesky-social/indigo/api/atproto" 17 + "github.com/bluesky-social/indigo/api/bsky" 15 18 "github.com/bluesky-social/indigo/atproto/syntax" 19 + "github.com/bluesky-social/indigo/lex/util" 20 + "github.com/bluesky-social/indigo/xrpc" 21 + "github.com/gorilla/sessions" 16 22 oauth "github.com/haileyok/atproto-oauth-golang" 17 23 _ "github.com/joho/godotenv/autoload" 24 + "github.com/labstack/echo-contrib/session" 18 25 "github.com/labstack/echo/v4" 19 26 "github.com/lestrrat-go/jwx/v2/jwk" 20 27 slogecho "github.com/samber/slog-echo" 21 28 "github.com/urfave/cli/v2" 22 29 "gorm.io/driver/sqlite" 23 30 "gorm.io/gorm" 31 + "gorm.io/gorm/clause" 24 32 ) 25 33 26 34 var ( ··· 28 36 serverAddr = os.Getenv("OAUTH_TEST_SERVER_ADDR") 29 37 serverUrlRoot = os.Getenv("OAUTH_TEST_SERVER_URL_ROOT") 30 38 staticFilePath = os.Getenv("OAUTH_TEST_SERVER_STATIC_PATH") 39 + sessionSecret = os.Getenv("OAUTH_TEST_SESSION_SECRET") 31 40 serverMetadataUrl = fmt.Sprintf("%s/oauth/client-metadata.json", serverUrlRoot) 32 41 serverCallbackUrl = fmt.Sprintf("%s/callback", serverUrlRoot) 33 42 pdsUrl = os.Getenv("OAUTH_TEST_PDS_URL") ··· 52 61 e *echo.Echo 53 62 db *gorm.DB 54 63 oauthClient *oauth.OauthClient 64 + xrpcCli *oauth.XrpcClient 55 65 jwksResponse *oauth.JwksResponseObject 56 66 } 57 67 68 + type TemplateRenderer struct { 69 + templates *template.Template 70 + } 71 + 72 + func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error { 73 + if viewContext, isMap := data.(map[string]interface{}); isMap { 74 + viewContext["reverse"] = c.Echo().Reverse 75 + } 76 + 77 + return t.templates.ExecuteTemplate(w, name, data) 78 + } 79 + 58 80 func run(cmd *cli.Context) error { 59 81 s, err := NewServer() 60 82 if err != nil { ··· 70 92 e := echo.New() 71 93 72 94 e.Use(slogecho.New(slog.Default())) 95 + e.Use(session.Middleware(sessions.NewCookieStore([]byte(sessionSecret)))) 96 + 97 + renderer := &TemplateRenderer{ 98 + templates: template.Must(template.ParseGlob(getFilePath("*.html"))), 99 + } 100 + e.Renderer = renderer 73 101 74 102 fmt.Println("atproto oauth golang tester server") 75 103 ··· 112 140 return nil, err 113 141 } 114 142 115 - db.AutoMigrate(&OauthRequest{}) 143 + db.AutoMigrate(&OauthRequest{}, &OauthSession{}) 144 + 145 + xrpcCli := &oauth.XrpcClient{ 146 + OnDPoPNonceChanged: func(did, newNonce string) { 147 + if err := db.Exec("UPDATE oauth_sessions SET dpop_pds_nonce = ? WHERE did = ?", newNonce, did).Error; err != nil { 148 + slog.Default().Error("error updating pds nonce", "err", err) 149 + } 150 + }, 151 + } 116 152 117 153 return &TestServer{ 118 154 httpd: httpd, 119 155 e: e, 120 156 db: db, 121 157 oauthClient: c, 158 + xrpcCli: xrpcCli, 122 159 jwksResponse: oauth.CreateJwksResponseObject(pubKey), 123 160 }, nil 124 161 } 125 162 126 163 func (s *TestServer) run() error { 127 - s.e.File("/", s.getFilePath("index.html")) 128 - s.e.File("/login", s.getFilePath("login.html")) 164 + s.e.GET("/", s.handleHome) 165 + s.e.File("/login", getFilePath("login.html")) 129 166 s.e.POST("/login", s.handleLoginSubmit) 167 + s.e.GET("/logout", s.handleLogout) 168 + s.e.GET("/make-post", s.handleMakePost) 169 + s.e.GET("/callback", s.handleCallback) 130 170 s.e.GET("/oauth/client-metadata.json", s.handleClientMetadata) 131 171 s.e.GET("/oauth/jwks.json", s.handleJwks) 132 172 ··· 135 175 } 136 176 137 177 return nil 178 + } 179 + 180 + func (s *TestServer) handleHome(e echo.Context) error { 181 + sess, err := session.Get("session", e) 182 + if err != nil { 183 + return err 184 + } 185 + 186 + return e.Render(200, "index.html", map[string]any{ 187 + "Did": sess.Values["did"], 188 + }) 138 189 } 139 190 140 191 func (s *TestServer) handleClientMetadata(e echo.Context) error { ··· 223 274 dpopPrivateKey, 224 275 ) 225 276 226 - oauthRequest := OauthRequest{ 227 - State: "", 277 + oauthRequest := &OauthRequest{ 278 + State: parResp.State, 228 279 AuthserverIss: meta.Issuer, 229 280 Did: did, 230 281 PdsUrl: service, ··· 233 284 DpopPrivateJwk: string(dpopPrivateKeyJson), 234 285 } 235 286 236 - if err := s.db.Create(&oauthRequest).Error; err != nil { 287 + if err := s.db.Create(oauthRequest).Error; err != nil { 237 288 return err 238 289 } 239 290 ··· 244 295 parResp.Resp["request_uri"].(string), 245 296 ) 246 297 298 + sess, err := session.Get("session", e) 299 + if err != nil { 300 + return err 301 + } 302 + 303 + sess.Options = &sessions.Options{ 304 + Path: "/", 305 + MaxAge: 300, // save for five minutes 306 + HttpOnly: true, 307 + } 308 + 309 + // make sure the session is empty 310 + sess.Values = map[interface{}]interface{}{} 311 + sess.Values["oauth_state"] = parResp.State 312 + sess.Values["oauth_did"] = did 313 + 314 + if err := sess.Save(e.Request(), e.Response()); err != nil { 315 + return err 316 + } 317 + 247 318 return e.Redirect(302, u.String()) 248 319 } 249 320 321 + func (s *TestServer) handleCallback(e echo.Context) error { 322 + resState := e.QueryParam("state") 323 + resIss := e.QueryParam("iss") 324 + resCode := e.QueryParam("code") 325 + 326 + sess, err := session.Get("session", e) 327 + if err != nil { 328 + return err 329 + } 330 + 331 + sessState := sess.Values["oauth_state"] 332 + sessDid := sess.Values["oauth_did"] 333 + 334 + if resState == "" || resIss == "" || resCode == "" || sessState == "" || sessDid == "" { 335 + return fmt.Errorf("request missing needed parameters") 336 + } 337 + 338 + if resState != sessState { 339 + return fmt.Errorf("session state does not match response state") 340 + } 341 + 342 + var oauthRequest OauthRequest 343 + if err := s.db.Raw("SELECT * FROM oauth_requests WHERE state = ? AND did = ?", sessState, sessDid).Scan(&oauthRequest).Error; err != nil { 344 + return err 345 + } 346 + 347 + if err := s.db.Exec("DELETE FROM oauth_requests WHERE state = ? AND did = ?", sessState, sessDid).Error; err != nil { 348 + return err 349 + } 350 + 351 + if resIss != oauthRequest.AuthserverIss { 352 + return fmt.Errorf("incoming iss did not match authserver iss") 353 + } 354 + 355 + jwk, err := oauth.ParseKeyFromBytes([]byte(oauthRequest.DpopPrivateJwk)) 356 + if err != nil { 357 + return err 358 + } 359 + 360 + initialTokenResp, err := s.oauthClient.InitialTokenRequest( 361 + e.Request().Context(), 362 + resCode, 363 + resIss, 364 + resIss, 365 + oauthRequest.PkceVerifier, 366 + oauthRequest.DpopAuthserverNonce, 367 + jwk, 368 + ) 369 + if err != nil { 370 + return err 371 + } 372 + 373 + // TODO: resolve if needed 374 + 375 + if initialTokenResp.Resp["scope"] != scope { 376 + return fmt.Errorf("did not receive correct scopes from token request") 377 + } 378 + 379 + oauthSession := &OauthSession{ 380 + Did: oauthRequest.Did, 381 + PdsUrl: oauthRequest.PdsUrl, 382 + AuthserverIss: oauthRequest.AuthserverIss, 383 + AccessToken: initialTokenResp.Resp["access_token"].(string), 384 + RefreshToken: initialTokenResp.Resp["refresh_token"].(string), 385 + DpopAuthserverNonce: initialTokenResp.DpopAuthserverNonce, 386 + DpopPrivateJwk: oauthRequest.DpopPrivateJwk, 387 + } 388 + 389 + if err := s.db.Clauses(clause.OnConflict{ 390 + Columns: []clause.Column{{Name: "did"}}, 391 + UpdateAll: true, 392 + }).Create(oauthSession).Error; err != nil { 393 + return err 394 + } 395 + 396 + sess.Options = &sessions.Options{ 397 + Path: "/", 398 + MaxAge: 86400 * 7, 399 + HttpOnly: true, 400 + } 401 + 402 + // make sure the session is empty 403 + sess.Values = map[interface{}]interface{}{} 404 + sess.Values["did"] = oauthRequest.Did 405 + 406 + if err := sess.Save(e.Request(), e.Response()); err != nil { 407 + return err 408 + } 409 + 410 + return e.Redirect(302, "/") 411 + } 412 + 413 + func (s *TestServer) handleLogout(e echo.Context) error { 414 + sess, err := session.Get("session", e) 415 + if err != nil { 416 + return err 417 + } 418 + 419 + sess.Options = &sessions.Options{ 420 + Path: "/", 421 + MaxAge: -1, 422 + HttpOnly: true, 423 + } 424 + 425 + if err := sess.Save(e.Request(), e.Response()); err != nil { 426 + return err 427 + } 428 + 429 + return e.Redirect(302, "/") 430 + } 431 + 432 + func (s *TestServer) handleMakePost(e echo.Context) error { 433 + sess, err := session.Get("session", e) 434 + if err != nil { 435 + return err 436 + } 437 + 438 + did, ok := sess.Values["did"] 439 + if !ok { 440 + return e.Redirect(302, "/login") 441 + } 442 + 443 + var oauthSession OauthSession 444 + if err := s.db.Raw("SELECT * FROM oauth_sessions WHERE did = ?", did).Scan(&oauthSession).Error; err != nil { 445 + return err 446 + } 447 + 448 + args, err := authedReqArgsFromSession(&oauthSession) 449 + if err != nil { 450 + return err 451 + } 452 + 453 + post := bsky.FeedPost{ 454 + Text: "hello from atproto golang oauth client", 455 + CreatedAt: syntax.DatetimeNow().String(), 456 + } 457 + 458 + input := atproto.RepoCreateRecord_Input{ 459 + Collection: "app.bsky.feed.post", 460 + Repo: oauthSession.Did, 461 + Record: &util.LexiconTypeDecoder{Val: &post}, 462 + } 463 + 464 + var out atproto.RepoCreateRecord_Output 465 + if err := s.xrpcCli.Do(e.Request().Context(), args, xrpc.Procedure, "application/json", "com.atproto.repo.createRecord", nil, input, &out); err != nil { 466 + return err 467 + } 468 + 469 + return e.File(getFilePath("make-post.html")) 470 + } 471 + 472 + func authedReqArgsFromSession(session *OauthSession) (*oauth.XrpcAuthedRequestArgs, error) { 473 + privateJwk, err := oauth.ParseKeyFromBytes([]byte(session.DpopPrivateJwk)) 474 + if err != nil { 475 + return nil, err 476 + } 477 + 478 + return &oauth.XrpcAuthedRequestArgs{ 479 + Did: session.Did, 480 + AccessToken: session.AccessToken, 481 + PdsUrl: session.PdsUrl, 482 + Issuer: session.AuthserverIss, 483 + DpopPdsNonce: session.DpopPdsNonce, 484 + DpopPrivateJwk: privateJwk, 485 + }, nil 486 + } 487 + 250 488 func resolveHandle(ctx context.Context, handle string) (string, error) { 251 489 var did string 252 490 ··· 303 541 did = maybeDid 304 542 } 305 543 306 - // TODO: we can also support did:web here 307 - 308 - if did == "" { 309 - return "", fmt.Errorf("unable to resolve handle") 310 - } 311 - 312 544 return did, nil 313 545 } 314 546 ··· 321 553 } `json:"service"` 322 554 } 323 555 556 + var ustr string 324 557 if strings.HasPrefix(did, "did:plc:") { 325 - req, err := http.NewRequestWithContext( 326 - ctx, 327 - "GET", 328 - fmt.Sprintf("https://plc.directory/%s", did), 329 - nil, 330 - ) 331 - if err != nil { 332 - return "", err 333 - } 334 - 335 - resp, err := http.DefaultClient.Do(req) 336 - if err != nil { 337 - return "", err 338 - } 339 - defer resp.Body.Close() 340 - 341 - if resp.StatusCode != 200 { 342 - io.Copy(io.Discard, resp.Body) 343 - return "", fmt.Errorf("could not find identity in plc registry") 344 - } 345 - 346 - var identity Identity 347 - if err := json.NewDecoder(resp.Body).Decode(&identity); err != nil { 348 - return "", err 349 - } 350 - 351 - var service string 352 - for _, svc := range identity.Service { 353 - if svc.ID == "#atproto_pds" { 354 - service = svc.ServiceEndpoint 355 - } 356 - } 357 - 358 - if service == "" { 359 - return "", fmt.Errorf("could not find atproto_pds service in identity services") 360 - } 361 - 362 - return service, nil 558 + ustr = fmt.Sprintf("https://plc.directory/%s", did) 363 559 } else if strings.HasPrefix(did, "did:web:") { 364 - // TODO: needs more work 365 - req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://%s/.well-known/did.json", did), nil) 366 - if err != nil { 367 - return "", err 368 - } 560 + ustr = fmt.Sprintf("https://%s/.well-known/did.json", did) 561 + } else { 562 + return "", fmt.Errorf("did was not a supported did type") 563 + } 369 564 370 - resp, err := http.DefaultClient.Do(req) 371 - if err != nil { 372 - return "", err 373 - } 374 - defer resp.Body.Close() 565 + req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil) 566 + if err != nil { 567 + return "", err 568 + } 375 569 376 - if resp.StatusCode != 200 { 377 - io.Copy(io.Discard, resp.Body) 378 - return "", fmt.Errorf("could not find identity in plc registry") 379 - } 570 + resp, err := http.DefaultClient.Do(req) 571 + if err != nil { 572 + return "", err 573 + } 574 + defer resp.Body.Close() 380 575 381 - var identity Identity 382 - if err := json.NewDecoder(resp.Body).Decode(&identity); err != nil { 383 - return "", err 384 - } 576 + if resp.StatusCode != 200 { 577 + io.Copy(io.Discard, resp.Body) 578 + return "", fmt.Errorf("could not find identity in plc registry") 579 + } 385 580 386 - var service string 387 - for _, svc := range identity.Service { 388 - if svc.ID == "#atproto_pds" { 389 - service = svc.ServiceEndpoint 390 - } 391 - } 581 + var identity Identity 582 + if err := json.NewDecoder(resp.Body).Decode(&identity); err != nil { 583 + return "", err 584 + } 392 585 393 - if service == "" { 394 - return "", fmt.Errorf("could not find atproto_pds service in identity services") 586 + var service string 587 + for _, svc := range identity.Service { 588 + if svc.ID == "#atproto_pds" { 589 + service = svc.ServiceEndpoint 395 590 } 591 + } 396 592 397 - return service, nil 398 - } else { 399 - return "", fmt.Errorf("did was not a supported did type") 593 + if service == "" { 594 + return "", fmt.Errorf("could not find atproto_pds service in identity services") 400 595 } 596 + 597 + return service, nil 401 598 } 402 599 403 - func (s *TestServer) getFilePath(file string) string { 600 + func getFilePath(file string) string { 404 601 return fmt.Sprintf("%s/%s", staticFilePath, file) 405 602 }
+13 -1
cmd/client_test/types.go
··· 3 3 type OauthRequest struct { 4 4 ID uint 5 5 AuthserverIss string 6 - State string 6 + State string `gorm:"index"` 7 7 Did string `gorm:"index"` 8 8 PdsUrl string 9 9 PkceVerifier string 10 10 DpopAuthserverNonce string 11 11 DpopPrivateJwk string 12 12 } 13 + 14 + type OauthSession struct { 15 + ID uint 16 + Did string `gorm:"uniqueIndex"` 17 + PdsUrl string 18 + AuthserverIss string 19 + AccessToken string 20 + RefreshToken string 21 + DpopPdsNonce string 22 + DpopAuthserverNonce string 23 + DpopPrivateJwk string 24 + }
+45
go.mod
··· 6 6 github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188 7 7 github.com/golang-jwt/jwt/v5 v5.2.1 8 8 github.com/google/uuid v1.6.0 9 + github.com/gorilla/sessions v1.4.0 9 10 github.com/joho/godotenv v1.5.1 11 + github.com/labstack/echo-contrib v0.17.2 10 12 github.com/labstack/echo/v4 v4.13.3 11 13 github.com/lestrrat-go/jwx/v2 v2.0.12 12 14 github.com/samber/slog-echo v1.15.1 ··· 17 19 ) 18 20 19 21 require ( 22 + github.com/carlmjohnson/versioninfo v0.22.5 // indirect 20 23 github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect 21 24 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 22 25 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect 26 + github.com/felixge/httpsnoop v1.0.4 // indirect 27 + github.com/go-logr/logr v1.4.2 // indirect 28 + github.com/go-logr/stdr v1.2.2 // indirect 23 29 github.com/goccy/go-json v0.10.2 // indirect 30 + github.com/gogo/protobuf v1.3.2 // indirect 31 + github.com/gorilla/context v1.1.2 // indirect 32 + github.com/gorilla/securecookie v1.1.2 // 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 38 + 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 24 49 github.com/jinzhu/inflection v1.0.0 // indirect 25 50 github.com/jinzhu/now v1.1.5 // indirect 51 + github.com/klauspost/cpuid/v2 v2.2.7 // indirect 26 52 github.com/labstack/gommon v0.4.2 // indirect 27 53 github.com/lestrrat-go/blackmagic v1.0.2 // indirect 28 54 github.com/lestrrat-go/httpcc v1.0.1 // indirect ··· 32 58 github.com/mattn/go-colorable v0.1.13 // indirect 33 59 github.com/mattn/go-isatty v0.0.20 // indirect 34 60 github.com/mattn/go-sqlite3 v1.14.22 // indirect 61 + github.com/minio/sha256-simd v1.0.1 // indirect 62 + github.com/mr-tron/base58 v1.2.0 // indirect 63 + github.com/multiformats/go-base32 v0.1.0 // indirect 64 + github.com/multiformats/go-base36 v0.2.0 // indirect 65 + github.com/multiformats/go-multibase v0.2.0 // indirect 66 + github.com/multiformats/go-multihash v0.2.3 // indirect 67 + github.com/multiformats/go-varint v0.0.7 // indirect 68 + github.com/opentracing/opentracing-go v1.2.0 // indirect 35 69 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 70 + github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 36 71 github.com/rogpeppe/go-internal v1.13.1 // indirect 37 72 github.com/russross/blackfriday/v2 v2.1.0 // indirect 38 73 github.com/samber/lo v1.47.0 // indirect 39 74 github.com/segmentio/asm v1.2.0 // indirect 75 + github.com/spaolacci/murmur3 v1.1.0 // indirect 40 76 github.com/valyala/bytebufferpool v1.0.0 // indirect 41 77 github.com/valyala/fasttemplate v1.2.2 // indirect 78 + github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e // indirect 42 79 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect 80 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect 43 81 go.opentelemetry.io/otel v1.29.0 // indirect 82 + go.opentelemetry.io/otel/metric v1.29.0 // indirect 44 83 go.opentelemetry.io/otel/trace v1.29.0 // indirect 84 + go.uber.org/atomic v1.11.0 // indirect 85 + go.uber.org/multierr v1.11.0 // indirect 86 + go.uber.org/zap v1.26.0 // indirect 45 87 golang.org/x/crypto v0.31.0 // indirect 46 88 golang.org/x/net v0.33.0 // indirect 47 89 golang.org/x/sys v0.28.0 // indirect 48 90 golang.org/x/text v0.21.0 // indirect 91 + golang.org/x/time v0.8.0 // indirect 92 + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect 49 93 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 50 94 gopkg.in/yaml.v3 v3.0.1 // indirect 95 + lukechampine.com/blake3 v1.2.1 // indirect 51 96 )
+178
go.sum
··· 1 + github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 + github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 1 3 github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188 h1:1sQaG37xk08/rpmdhrmMkfQWF9kZbnfHm9Zav3bbSMk= 2 4 github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188/go.mod h1:NVBwZvbBSa93kfyweAmKwOLYawdVHdwZ9s+GZtBBVLA= 5 + github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc= 6 + github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8= 7 + github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 3 8 github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= 4 9 github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 5 10 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= ··· 9 14 github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 10 15 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= 11 16 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 17 + github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 18 + github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 19 + github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 20 + github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 21 + github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 22 + github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 23 + github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 24 + github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= 12 25 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 13 26 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 27 + github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 28 + github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 14 29 github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= 15 30 github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 16 31 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 17 32 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 33 + github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 34 + github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 35 + github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 18 36 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 19 37 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 38 + github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 39 + github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 40 + github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= 41 + github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= 42 + github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 43 + github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 44 + github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= 45 + github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= 46 + github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 47 + github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 48 + github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= 49 + github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 50 + github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= 51 + github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= 52 + github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 53 + github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 54 + github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= 55 + github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= 56 + github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= 57 + github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= 58 + github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= 59 + github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= 60 + github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= 61 + github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= 62 + github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= 63 + github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= 64 + github.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ= 65 + github.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE= 66 + github.com/ipfs/go-ipfs-ds-help v1.1.1 h1:B5UJOH52IbcfS56+Ul+sv8jnIV10lbjLF5eOO0C66Nw= 67 + github.com/ipfs/go-ipfs-ds-help v1.1.1/go.mod h1:75vrVCkSdSFidJscs8n4W+77AtTpCIAdDGAwjitJMIo= 68 + github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= 69 + github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= 70 + github.com/ipfs/go-ipld-cbor v0.1.0 h1:dx0nS0kILVivGhfWuB6dUpMa/LAwElHPw1yOGYopoYs= 71 + github.com/ipfs/go-ipld-cbor v0.1.0/go.mod h1:U2aYlmVrJr2wsUBU67K4KgepApSZddGRDWBYR0H4sCk= 72 + github.com/ipfs/go-ipld-format v0.6.0 h1:VEJlA2kQ3LqFSIm5Vu6eIlSxD/Ze90xtc4Meten1F5U= 73 + github.com/ipfs/go-ipld-format v0.6.0/go.mod h1:g4QVMTn3marU3qXchwjpKPKgJv+zF+OlaKMyhJ4LHPg= 74 + github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= 75 + github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= 76 + github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= 77 + github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= 78 + github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= 79 + github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= 80 + github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= 81 + github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= 82 + github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= 83 + github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= 20 84 github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 21 85 github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 22 86 github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 23 87 github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 24 88 github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 25 89 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 90 + github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 91 + github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 92 + github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 93 + github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 94 + github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 95 + github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 96 + github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 26 97 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 27 98 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 28 99 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= ··· 30 101 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 31 102 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 32 103 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 104 + github.com/labstack/echo-contrib v0.17.2 h1:K1zivqmtcC70X9VdBFdLomjPDEVHlrcAObqmuFj1c6w= 105 + github.com/labstack/echo-contrib v0.17.2/go.mod h1:NeDh3PX7j/u+jR4iuDt1zHmWZSCz9c/p9mxXcDpyS8E= 33 106 github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= 34 107 github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= 35 108 github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= ··· 50 123 github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 51 124 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 52 125 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 126 + github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 53 127 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 54 128 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 55 129 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 56 130 github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 57 131 github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 132 + github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 133 + github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 134 + github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 135 + github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 136 + github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= 137 + github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= 138 + github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= 139 + github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= 140 + github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= 141 + github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= 142 + github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= 143 + github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= 144 + github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= 145 + github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= 146 + github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 147 + github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 148 + github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 58 149 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 59 150 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 60 151 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 152 + github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= 153 + github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= 154 + github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 61 155 github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 62 156 github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 157 + github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 63 158 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 64 159 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 65 160 github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= ··· 68 163 github.com/samber/slog-echo v1.15.1/go.mod h1:K21nbusPmai/MYm8PFactmZoFctkMmkeaTdXXyvhY1c= 69 164 github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 70 165 github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 166 + github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 167 + github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= 168 + github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 169 + github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= 170 + github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= 171 + github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 172 + github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 71 173 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 72 174 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 73 175 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 176 + github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 177 + github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 178 + github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 74 179 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 180 + github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 75 181 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 76 182 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 77 183 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 78 184 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 79 185 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 186 + github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 80 187 github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= 81 188 github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= 82 189 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 83 190 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 84 191 github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 85 192 github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 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 + github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4= 196 + github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 86 197 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= 87 198 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= 199 + github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 200 + github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 201 + github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 88 202 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 203 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= 204 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= 89 205 go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= 90 206 go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= 207 + go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= 208 + go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= 91 209 go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= 92 210 go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= 211 + go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 212 + go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 213 + go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 214 + go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 215 + go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 216 + go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= 217 + go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= 218 + go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 219 + go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 220 + go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 221 + go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 222 + go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 223 + go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= 224 + go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 225 + go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 226 + go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 93 227 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 228 + golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 229 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 230 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 94 231 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 95 232 golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= 96 233 golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 97 234 golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 235 + golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 236 + golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 237 + golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 238 + golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 239 + golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 98 240 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 99 241 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 242 + golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 243 + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 100 244 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 245 + golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 246 + golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 101 247 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 248 + golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 102 249 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 103 250 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 104 251 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 105 252 golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 106 253 golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 107 254 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 255 + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 256 + golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 257 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 108 258 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 109 259 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 110 260 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 261 + golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 262 + golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 111 263 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 264 + golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 265 + golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 112 266 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 267 + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 113 268 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 114 269 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 115 270 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= ··· 133 288 golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 134 289 golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 135 290 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 291 + golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= 292 + golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 136 293 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 294 + golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 295 + golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 296 + golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 297 + golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 298 + golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 137 299 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 300 + golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 301 + golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 302 + golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 138 303 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 139 304 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 140 305 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 306 + golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 307 + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 308 + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 309 + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 310 + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 141 311 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 312 + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 142 313 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 143 314 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 315 + gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 316 + gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 317 + gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 144 318 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 319 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 145 320 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 146 321 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 147 322 gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= 148 323 gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= 149 324 gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8= 150 325 gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= 326 + honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 327 + lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= 328 + lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
+17 -28
oauth.go
··· 174 174 return tokenString, nil 175 175 } 176 176 177 - func (c *OauthClient) AuthServerDpopJwt( 178 - method, url, nonce string, 179 - privateJwk jwk.Key, 180 - ) (string, error) { 177 + func (c *OauthClient) AuthServerDpopJwt(method, url, nonce string, privateJwk jwk.Key) (string, error) { 181 178 pubJwk, err := privateJwk.PublicKey() 182 179 if err != nil { 183 180 return "", err ··· 232 229 Resp map[string]any 233 230 } 234 231 235 - func (c *OauthClient) SendParAuthRequest( 236 - ctx context.Context, 237 - authServerUrl string, 238 - authServerMeta *OauthAuthorizationMetadata, 239 - loginHint, scope string, 240 - dpopPrivateKey jwk.Key, 241 - ) (*SendParAuthResponse, error) { 232 + func (c *OauthClient) SendParAuthRequest(ctx context.Context, authServerUrl string, authServerMeta *OauthAuthorizationMetadata, loginHint, scope string, dpopPrivateKey jwk.Key) (*SendParAuthResponse, error) { 242 233 if authServerMeta == nil { 243 234 return nil, fmt.Errorf("nil metadata provided") 244 235 } ··· 340 331 if err := json.NewDecoder(resp2.Body).Decode(&rmap); err != nil { 341 332 return nil, err 342 333 } 334 + 335 + fmt.Println(rmap) 343 336 } 344 337 345 338 return &SendParAuthResponse{ ··· 352 345 353 346 type TokenResponse struct { 354 347 DpopAuthserverNonce string 355 - Resp map[string]string 348 + Resp map[string]any 356 349 } 357 350 358 351 func (c *OauthClient) InitialTokenRequest( 359 352 ctx context.Context, 360 - authRequest map[string]string, 361 - code, appUrl string, 353 + code, 354 + appUrl, 355 + authserverIss, 356 + pkceVerifier, 357 + dpopAuthserverNonce string, 358 + dpopPrivateJwk jwk.Key, 362 359 ) (*TokenResponse, error) { 363 - authserverUrl := authRequest["authserver_iss"] 364 - authserverMeta, err := c.FetchAuthServerMetadata(ctx, authserverUrl) 360 + authserverMeta, err := c.FetchAuthServerMetadata(ctx, authserverIss) 365 361 if err != nil { 366 362 return nil, err 367 363 } 368 364 369 - clientAssertion, err := c.ClientAssertionJwt(authserverUrl) 365 + clientAssertion, err := c.ClientAssertionJwt(authserverIss) 370 366 if err != nil { 371 367 return nil, err 372 368 } ··· 376 372 "redirect_uri": {c.redirectUri}, 377 373 "grant_type": {"authorization_code"}, 378 374 "code": {code}, 379 - "code_verifier": {authRequest["pkce_verifier"]}, 375 + "code_verifier": {pkceVerifier}, 380 376 "client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"}, 381 377 "client_assertion": {clientAssertion}, 382 378 } 383 379 384 - dpopPrivateJwk, err := parsePrivateJwkFromString(authRequest["dpop_private_jwk"]) 385 - if err != nil { 386 - return nil, err 387 - } 388 - 389 380 dpopProof, err := c.AuthServerDpopJwt( 390 381 "POST", 391 382 authserverMeta.TokenEndpoint, 392 - authRequest["dpop_authserver_nonce"], 383 + dpopAuthserverNonce, 393 384 dpopPrivateJwk, 394 385 ) 395 386 if err != nil { 396 387 return nil, err 397 388 } 398 - 399 - dpopAuthserverNonce := authRequest["dpop_authserver_nonce"] 400 389 401 390 req, err := http.NewRequestWithContext( 402 391 ctx, ··· 419 408 420 409 // TODO: use nonce if needed, same as in par 421 410 422 - var rmap map[string]string 411 + var rmap map[string]any 423 412 if err := json.NewDecoder(resp.Body).Decode(&rmap); err != nil { 424 413 return nil, err 425 414 } ··· 501 490 return nil, fmt.Errorf("token refresh error: %s", string(b)) 502 491 } 503 492 504 - var rmap map[string]string 493 + var rmap map[string]any 505 494 if err := json.NewDecoder(resp.Body).Decode(&rmap); err != nil { 506 495 return nil, err 507 496 }
+247
xrpc.go
··· 1 + package oauth 2 + 3 + import ( 4 + "bytes" 5 + "context" 6 + "encoding/json" 7 + "fmt" 8 + "io" 9 + "net/http" 10 + "net/url" 11 + "strconv" 12 + "time" 13 + 14 + "github.com/bluesky-social/indigo/util" 15 + "github.com/bluesky-social/indigo/xrpc" 16 + "github.com/carlmjohnson/versioninfo" 17 + "github.com/golang-jwt/jwt/v5" 18 + "github.com/google/uuid" 19 + "github.com/lestrrat-go/jwx/v2/jwk" 20 + ) 21 + 22 + type XrpcClient struct { 23 + // Client is an HTTP client to use. If not set, defaults to http.RobustHTTPClient(). 24 + Client *http.Client 25 + UserAgent *string 26 + Headers map[string]string 27 + OnDPoPNonceChanged func(did, newNonce string) 28 + } 29 + 30 + func (c *XrpcClient) getClient() *http.Client { 31 + if c.Client == nil { 32 + return util.RobustHTTPClient() 33 + } 34 + return c.Client 35 + } 36 + 37 + func errorFromHTTPResponse(resp *http.Response, err error) error { 38 + r := &xrpc.Error{ 39 + StatusCode: resp.StatusCode, 40 + Wrapped: err, 41 + } 42 + if resp.Header.Get("ratelimit-limit") != "" { 43 + r.Ratelimit = &xrpc.RatelimitInfo{ 44 + Policy: resp.Header.Get("ratelimit-policy"), 45 + } 46 + if n, err := strconv.ParseInt(resp.Header.Get("ratelimit-reset"), 10, 64); err == nil { 47 + r.Ratelimit.Reset = time.Unix(n, 0) 48 + } 49 + if n, err := strconv.ParseInt(resp.Header.Get("ratelimit-limit"), 10, 64); err == nil { 50 + r.Ratelimit.Limit = int(n) 51 + } 52 + if n, err := strconv.ParseInt(resp.Header.Get("ratelimit-remaining"), 10, 64); err == nil { 53 + r.Ratelimit.Remaining = int(n) 54 + } 55 + } 56 + return r 57 + } 58 + 59 + // makeParams converts a map of string keys and any values into a URL-encoded string. 60 + // If a value is a slice of strings, it will be joined with commas. 61 + // Generally the values will be strings, numbers, booleans, or slices of strings 62 + func makeParams(p map[string]any) string { 63 + params := url.Values{} 64 + for k, v := range p { 65 + if s, ok := v.([]string); ok { 66 + for _, v := range s { 67 + params.Add(k, v) 68 + } 69 + } else { 70 + params.Add(k, fmt.Sprint(v)) 71 + } 72 + } 73 + 74 + return params.Encode() 75 + } 76 + 77 + func PdsDpopJwt(method, url, iss, accessToken, nonce string, privateJwk jwk.Key) (string, error) { 78 + pubJwk, err := privateJwk.PublicKey() 79 + if err != nil { 80 + return "", err 81 + } 82 + 83 + b, err := json.Marshal(pubJwk) 84 + if err != nil { 85 + return "", err 86 + } 87 + 88 + var pubMap map[string]any 89 + if err := json.Unmarshal(b, &pubMap); err != nil { 90 + return "", err 91 + } 92 + 93 + now := time.Now().Unix() 94 + 95 + claims := jwt.MapClaims{ 96 + "iss": iss, 97 + "iat": now, 98 + "exp": now + 30, 99 + "jti": uuid.NewString(), 100 + "htm": method, 101 + "htu": url, 102 + "ath": generateCodeChallenge(accessToken), 103 + } 104 + 105 + if nonce != "" { 106 + claims["nonce"] = nonce 107 + } 108 + 109 + token := jwt.NewWithClaims(jwt.SigningMethodES256, claims) 110 + token.Header["typ"] = "dpop+jwt" 111 + token.Header["alg"] = "ES256" 112 + token.Header["jwk"] = pubMap 113 + 114 + var rawKey any 115 + if err := privateJwk.Raw(&rawKey); err != nil { 116 + return "", err 117 + } 118 + 119 + tokenString, err := token.SignedString(rawKey) 120 + if err != nil { 121 + return "", fmt.Errorf("failed to sign token: %w", err) 122 + } 123 + 124 + return tokenString, nil 125 + } 126 + 127 + type XrpcAuthedRequestArgs struct { 128 + Did string 129 + PdsUrl string 130 + Issuer string 131 + AccessToken string 132 + DpopPdsNonce string 133 + DpopPrivateJwk jwk.Key 134 + } 135 + 136 + func (c *XrpcClient) Do(ctx context.Context, authedArgs *XrpcAuthedRequestArgs, kind xrpc.XRPCRequestType, inpenc, method string, params map[string]any, bodyobj any, out any) error { 137 + // we might have to retry the request if we get a new nonce from the server 138 + for range 2 { 139 + var body io.Reader 140 + if bodyobj != nil { 141 + if rr, ok := bodyobj.(io.Reader); ok { 142 + body = rr 143 + } else { 144 + b, err := json.Marshal(bodyobj) 145 + if err != nil { 146 + return err 147 + } 148 + 149 + body = bytes.NewReader(b) 150 + } 151 + } 152 + 153 + var m string 154 + switch kind { 155 + case xrpc.Query: 156 + m = "GET" 157 + case xrpc.Procedure: 158 + m = "POST" 159 + default: 160 + return fmt.Errorf("unsupported request kind: %d", kind) 161 + } 162 + 163 + var paramStr string 164 + if len(params) > 0 { 165 + paramStr = "?" + makeParams(params) 166 + } 167 + 168 + ustr := authedArgs.PdsUrl + "/xrpc/" + method + paramStr 169 + req, err := http.NewRequest(m, ustr, body) 170 + if err != nil { 171 + return err 172 + } 173 + 174 + if bodyobj != nil && inpenc != "" { 175 + req.Header.Set("Content-Type", inpenc) 176 + } 177 + if c.UserAgent != nil { 178 + req.Header.Set("User-Agent", *c.UserAgent) 179 + } else { 180 + req.Header.Set("User-Agent", "atproto-oauth/"+versioninfo.Short()) 181 + } 182 + 183 + if c.Headers != nil { 184 + for k, v := range c.Headers { 185 + req.Header.Set(k, v) 186 + } 187 + } 188 + 189 + if authedArgs != nil { 190 + dpopJwt, err := PdsDpopJwt(m, ustr, authedArgs.Issuer, authedArgs.AccessToken, authedArgs.DpopPdsNonce, authedArgs.DpopPrivateJwk) 191 + if err != nil { 192 + return err 193 + } 194 + 195 + req.Header.Set("DPoP", dpopJwt) 196 + req.Header.Set("Authorization", "DPoP "+authedArgs.AccessToken) 197 + } 198 + 199 + resp, err := c.getClient().Do(req.WithContext(ctx)) 200 + if err != nil { 201 + return fmt.Errorf("request failed: %w", err) 202 + } 203 + 204 + defer resp.Body.Close() 205 + 206 + if resp.StatusCode != 200 { 207 + var xe xrpc.XRPCError 208 + if err := json.NewDecoder(resp.Body).Decode(&xe); err != nil { 209 + return errorFromHTTPResponse(resp, fmt.Errorf("failed to decode xrpc error message: %w", err)) 210 + } 211 + 212 + // if we get a new nonce, update the nonce and make the request again 213 + if (resp.StatusCode == 400 || resp.StatusCode == 401) && xe.ErrStr == "use_dpop_nonce" { 214 + newNonce := resp.Header.Get("DPoP-Nonce") 215 + c.OnDPoPNonceChanged(authedArgs.Did, newNonce) 216 + authedArgs.DpopPdsNonce = newNonce 217 + continue 218 + } 219 + 220 + return errorFromHTTPResponse(resp, &xe) 221 + } 222 + 223 + if out != nil { 224 + if buf, ok := out.(*bytes.Buffer); ok { 225 + if resp.ContentLength < 0 { 226 + _, err := io.Copy(buf, resp.Body) 227 + if err != nil { 228 + return fmt.Errorf("reading response body: %w", err) 229 + } 230 + } else { 231 + n, err := io.CopyN(buf, resp.Body, resp.ContentLength) 232 + if err != nil { 233 + return fmt.Errorf("reading length delimited response body (%d < %d): %w", n, resp.ContentLength, err) 234 + } 235 + } 236 + } else { 237 + if err := json.NewDecoder(resp.Body).Decode(out); err != nil { 238 + return fmt.Errorf("decoding xrpc response: %w", err) 239 + } 240 + } 241 + } 242 + 243 + break 244 + } 245 + 246 + return nil 247 + }