this repo has no description
0
fork

Configure Feed

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

organize example

Hailey 8f8a47fd 497accf5

+414 -387
+229
cmd/client_test/handle_auth.go
··· 1 + package main 2 + 3 + import ( 4 + "encoding/json" 5 + "fmt" 6 + "net/url" 7 + 8 + "github.com/bluesky-social/indigo/atproto/syntax" 9 + "github.com/gorilla/sessions" 10 + oauth "github.com/haileyok/atproto-oauth-golang" 11 + "github.com/labstack/echo-contrib/session" 12 + "github.com/labstack/echo/v4" 13 + "gorm.io/gorm/clause" 14 + ) 15 + 16 + func (s *TestServer) handleLoginSubmit(e echo.Context) error { 17 + handle := e.FormValue("handle") 18 + if handle == "" { 19 + return e.Redirect(302, "/login?e=handle-empty") 20 + } 21 + 22 + _, herr := syntax.ParseHandle(handle) 23 + _, derr := syntax.ParseDID(handle) 24 + 25 + if herr != nil && derr != nil { 26 + return e.Redirect(302, "/login?e=handle-invalid") 27 + } 28 + 29 + var did string 30 + 31 + if derr == nil { 32 + did = handle 33 + } else { 34 + maybeDid, err := resolveHandle(e.Request().Context(), handle) 35 + if err != nil { 36 + return err 37 + } 38 + 39 + did = maybeDid 40 + } 41 + 42 + service, err := resolveService(ctx, did) 43 + if err != nil { 44 + return err 45 + } 46 + 47 + authserver, err := s.oauthClient.ResolvePDSAuthServer(ctx, service) 48 + if err != nil { 49 + return err 50 + } 51 + 52 + meta, err := s.oauthClient.FetchAuthServerMetadata(ctx, authserver) 53 + if err != nil { 54 + return err 55 + } 56 + 57 + dpopPrivateKey, err := oauth.GenerateKey(nil) 58 + if err != nil { 59 + return err 60 + } 61 + 62 + dpopPrivateKeyJson, err := json.Marshal(dpopPrivateKey) 63 + if err != nil { 64 + return err 65 + } 66 + 67 + parResp, err := s.oauthClient.SendParAuthRequest( 68 + ctx, 69 + authserver, 70 + meta, 71 + "", 72 + scope, 73 + dpopPrivateKey, 74 + ) 75 + 76 + oauthRequest := &OauthRequest{ 77 + State: parResp.State, 78 + AuthserverIss: meta.Issuer, 79 + Did: did, 80 + PdsUrl: service, 81 + PkceVerifier: parResp.PkceVerifier, 82 + DpopAuthserverNonce: parResp.DpopAuthserverNonce, 83 + DpopPrivateJwk: string(dpopPrivateKeyJson), 84 + } 85 + 86 + if err := s.db.Create(oauthRequest).Error; err != nil { 87 + return err 88 + } 89 + 90 + u, _ := url.Parse(meta.AuthorizationEndpoint) 91 + u.RawQuery = fmt.Sprintf( 92 + "client_id=%s&request_uri=%s", 93 + url.QueryEscape(serverMetadataUrl), 94 + parResp.Resp["request_uri"].(string), 95 + ) 96 + 97 + sess, err := session.Get("session", e) 98 + if err != nil { 99 + return err 100 + } 101 + 102 + sess.Options = &sessions.Options{ 103 + Path: "/", 104 + MaxAge: 300, // save for five minutes 105 + HttpOnly: true, 106 + } 107 + 108 + // make sure the session is empty 109 + sess.Values = map[interface{}]interface{}{} 110 + sess.Values["oauth_state"] = parResp.State 111 + sess.Values["oauth_did"] = did 112 + 113 + if err := sess.Save(e.Request(), e.Response()); err != nil { 114 + return err 115 + } 116 + 117 + return e.Redirect(302, u.String()) 118 + } 119 + 120 + func (s *TestServer) handleCallback(e echo.Context) error { 121 + resState := e.QueryParam("state") 122 + resIss := e.QueryParam("iss") 123 + resCode := e.QueryParam("code") 124 + 125 + sess, err := session.Get("session", e) 126 + if err != nil { 127 + return err 128 + } 129 + 130 + sessState := sess.Values["oauth_state"] 131 + sessDid := sess.Values["oauth_did"] 132 + 133 + if resState == "" || resIss == "" || resCode == "" || sessState == "" || sessDid == "" { 134 + return fmt.Errorf("request missing needed parameters") 135 + } 136 + 137 + if resState != sessState { 138 + return fmt.Errorf("session state does not match response state") 139 + } 140 + 141 + var oauthRequest OauthRequest 142 + if err := s.db.Raw("SELECT * FROM oauth_requests WHERE state = ? AND did = ?", sessState, sessDid).Scan(&oauthRequest).Error; err != nil { 143 + return err 144 + } 145 + 146 + if err := s.db.Exec("DELETE FROM oauth_requests WHERE state = ? AND did = ?", sessState, sessDid).Error; err != nil { 147 + return err 148 + } 149 + 150 + if resIss != oauthRequest.AuthserverIss { 151 + return fmt.Errorf("incoming iss did not match authserver iss") 152 + } 153 + 154 + jwk, err := oauth.ParseKeyFromBytes([]byte(oauthRequest.DpopPrivateJwk)) 155 + if err != nil { 156 + return err 157 + } 158 + 159 + initialTokenResp, err := s.oauthClient.InitialTokenRequest( 160 + e.Request().Context(), 161 + resCode, 162 + resIss, 163 + resIss, 164 + oauthRequest.PkceVerifier, 165 + oauthRequest.DpopAuthserverNonce, 166 + jwk, 167 + ) 168 + if err != nil { 169 + return err 170 + } 171 + 172 + // TODO: resolve if needed 173 + 174 + if initialTokenResp.Resp["scope"] != scope { 175 + return fmt.Errorf("did not receive correct scopes from token request") 176 + } 177 + 178 + oauthSession := &OauthSession{ 179 + Did: oauthRequest.Did, 180 + PdsUrl: oauthRequest.PdsUrl, 181 + AuthserverIss: oauthRequest.AuthserverIss, 182 + AccessToken: initialTokenResp.Resp["access_token"].(string), 183 + RefreshToken: initialTokenResp.Resp["refresh_token"].(string), 184 + DpopAuthserverNonce: initialTokenResp.DpopAuthserverNonce, 185 + DpopPrivateJwk: oauthRequest.DpopPrivateJwk, 186 + } 187 + 188 + if err := s.db.Clauses(clause.OnConflict{ 189 + Columns: []clause.Column{{Name: "did"}}, 190 + UpdateAll: true, 191 + }).Create(oauthSession).Error; err != nil { 192 + return err 193 + } 194 + 195 + sess.Options = &sessions.Options{ 196 + Path: "/", 197 + MaxAge: 86400 * 7, 198 + HttpOnly: true, 199 + } 200 + 201 + // make sure the session is empty 202 + sess.Values = map[interface{}]interface{}{} 203 + sess.Values["did"] = oauthRequest.Did 204 + 205 + if err := sess.Save(e.Request(), e.Response()); err != nil { 206 + return err 207 + } 208 + 209 + return e.Redirect(302, "/") 210 + } 211 + 212 + func (s *TestServer) handleLogout(e echo.Context) error { 213 + sess, err := session.Get("session", e) 214 + if err != nil { 215 + return err 216 + } 217 + 218 + sess.Options = &sessions.Options{ 219 + Path: "/", 220 + MaxAge: -1, 221 + HttpOnly: true, 222 + } 223 + 224 + if err := sess.Save(e.Request(), e.Response()); err != nil { 225 + return err 226 + } 227 + 228 + return e.Redirect(302, "/") 229 + }
+51
cmd/client_test/handle_post.go
··· 1 + package main 2 + 3 + import ( 4 + "github.com/bluesky-social/indigo/api/atproto" 5 + "github.com/bluesky-social/indigo/api/bsky" 6 + "github.com/bluesky-social/indigo/atproto/syntax" 7 + "github.com/bluesky-social/indigo/lex/util" 8 + "github.com/bluesky-social/indigo/xrpc" 9 + "github.com/labstack/echo-contrib/session" 10 + "github.com/labstack/echo/v4" 11 + ) 12 + 13 + func (s *TestServer) handleMakePost(e echo.Context) error { 14 + sess, err := session.Get("session", e) 15 + if err != nil { 16 + return err 17 + } 18 + 19 + did, ok := sess.Values["did"] 20 + if !ok { 21 + return e.Redirect(302, "/login") 22 + } 23 + 24 + var oauthSession OauthSession 25 + if err := s.db.Raw("SELECT * FROM oauth_sessions WHERE did = ?", did).Scan(&oauthSession).Error; err != nil { 26 + return err 27 + } 28 + 29 + args, err := authedReqArgsFromSession(&oauthSession) 30 + if err != nil { 31 + return err 32 + } 33 + 34 + post := bsky.FeedPost{ 35 + Text: "hello from atproto golang oauth client", 36 + CreatedAt: syntax.DatetimeNow().String(), 37 + } 38 + 39 + input := atproto.RepoCreateRecord_Input{ 40 + Collection: "app.bsky.feed.post", 41 + Repo: oauthSession.Did, 42 + Record: &util.LexiconTypeDecoder{Val: &post}, 43 + } 44 + 45 + var out atproto.RepoCreateRecord_Output 46 + if err := s.xrpcCli.Do(e.Request().Context(), args, xrpc.Procedure, "application/json", "com.atproto.repo.createRecord", nil, input, &out); err != nil { 47 + return err 48 + } 49 + 50 + return e.File(getFilePath("make-post.html")) 51 + }
-377
cmd/client_test/main.go
··· 2 2 3 3 import ( 4 4 "context" 5 - "encoding/json" 6 5 "fmt" 7 6 "html/template" 8 7 "io" 9 8 "log/slog" 10 - "net" 11 9 "net/http" 12 - "net/url" 13 10 "os" 14 - "strings" 15 11 16 - "github.com/bluesky-social/indigo/api/atproto" 17 - "github.com/bluesky-social/indigo/api/bsky" 18 - "github.com/bluesky-social/indigo/atproto/syntax" 19 - "github.com/bluesky-social/indigo/lex/util" 20 - "github.com/bluesky-social/indigo/xrpc" 21 12 "github.com/gorilla/sessions" 22 13 oauth "github.com/haileyok/atproto-oauth-golang" 23 14 _ "github.com/joho/godotenv/autoload" ··· 28 19 "github.com/urfave/cli/v2" 29 20 "gorm.io/driver/sqlite" 30 21 "gorm.io/gorm" 31 - "gorm.io/gorm/clause" 32 22 ) 33 23 34 24 var ( ··· 214 204 return e.JSON(200, s.jwksResponse) 215 205 } 216 206 217 - func (s *TestServer) handleLoginSubmit(e echo.Context) error { 218 - handle := e.FormValue("handle") 219 - if handle == "" { 220 - return e.Redirect(302, "/login?e=handle-empty") 221 - } 222 - 223 - _, herr := syntax.ParseHandle(handle) 224 - _, derr := syntax.ParseDID(handle) 225 - 226 - if herr != nil && derr != nil { 227 - return e.Redirect(302, "/login?e=handle-invalid") 228 - } 229 - 230 - var did string 231 - 232 - if derr == nil { 233 - did = handle 234 - } else { 235 - maybeDid, err := resolveHandle(e.Request().Context(), handle) 236 - if err != nil { 237 - return err 238 - } 239 - 240 - did = maybeDid 241 - } 242 - 243 - service, err := resolveService(ctx, did) 244 - if err != nil { 245 - return err 246 - } 247 - 248 - authserver, err := s.oauthClient.ResolvePDSAuthServer(ctx, service) 249 - if err != nil { 250 - return err 251 - } 252 - 253 - meta, err := s.oauthClient.FetchAuthServerMetadata(ctx, authserver) 254 - if err != nil { 255 - return err 256 - } 257 - 258 - dpopPrivateKey, err := oauth.GenerateKey(nil) 259 - if err != nil { 260 - return err 261 - } 262 - 263 - dpopPrivateKeyJson, err := json.Marshal(dpopPrivateKey) 264 - if err != nil { 265 - return err 266 - } 267 - 268 - parResp, err := s.oauthClient.SendParAuthRequest( 269 - ctx, 270 - authserver, 271 - meta, 272 - "", 273 - scope, 274 - dpopPrivateKey, 275 - ) 276 - 277 - oauthRequest := &OauthRequest{ 278 - State: parResp.State, 279 - AuthserverIss: meta.Issuer, 280 - Did: did, 281 - PdsUrl: service, 282 - PkceVerifier: parResp.PkceVerifier, 283 - DpopAuthserverNonce: parResp.DpopAuthserverNonce, 284 - DpopPrivateJwk: string(dpopPrivateKeyJson), 285 - } 286 - 287 - if err := s.db.Create(oauthRequest).Error; err != nil { 288 - return err 289 - } 290 - 291 - u, _ := url.Parse(meta.AuthorizationEndpoint) 292 - u.RawQuery = fmt.Sprintf( 293 - "client_id=%s&request_uri=%s", 294 - url.QueryEscape(serverMetadataUrl), 295 - parResp.Resp["request_uri"].(string), 296 - ) 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 - 318 - return e.Redirect(302, u.String()) 319 - } 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 207 func authedReqArgsFromSession(session *OauthSession) (*oauth.XrpcAuthedRequestArgs, error) { 473 208 privateJwk, err := oauth.ParseKeyFromBytes([]byte(session.DpopPrivateJwk)) 474 209 if err != nil { ··· 483 218 DpopPdsNonce: session.DpopPdsNonce, 484 219 DpopPrivateJwk: privateJwk, 485 220 }, nil 486 - } 487 - 488 - func resolveHandle(ctx context.Context, handle string) (string, error) { 489 - var did string 490 - 491 - _, err := syntax.ParseHandle(handle) 492 - if err != nil { 493 - return "", err 494 - } 495 - 496 - recs, err := net.LookupTXT(fmt.Sprintf("_atproto.%s", handle)) 497 - if err != nil { 498 - return "", err 499 - } 500 - 501 - for _, rec := range recs { 502 - if strings.HasPrefix(rec, "did=") { 503 - did = strings.Split(rec, "did=")[1] 504 - break 505 - } 506 - } 507 - 508 - if did == "" { 509 - req, err := http.NewRequestWithContext( 510 - ctx, 511 - "GET", 512 - fmt.Sprintf("https://%s/.well-known/atproto-did", handle), 513 - nil, 514 - ) 515 - if err != nil { 516 - return "", err 517 - } 518 - 519 - resp, err := http.DefaultClient.Do(req) 520 - if err != nil { 521 - return "", err 522 - } 523 - defer resp.Body.Close() 524 - 525 - if resp.StatusCode != http.StatusOK { 526 - io.Copy(io.Discard, resp.Body) 527 - return "", fmt.Errorf("unable to resolve handle") 528 - } 529 - 530 - b, err := io.ReadAll(resp.Body) 531 - if err != nil { 532 - return "", err 533 - } 534 - 535 - maybeDid := string(b) 536 - 537 - if _, err := syntax.ParseDID(maybeDid); err != nil { 538 - return "", fmt.Errorf("unable to resolve handle") 539 - } 540 - 541 - did = maybeDid 542 - } 543 - 544 - return did, nil 545 - } 546 - 547 - func resolveService(ctx context.Context, did string) (string, error) { 548 - type Identity struct { 549 - Service []struct { 550 - ID string `json:"id"` 551 - Type string `json:"type"` 552 - ServiceEndpoint string `json:"serviceEndpoint"` 553 - } `json:"service"` 554 - } 555 - 556 - var ustr string 557 - if strings.HasPrefix(did, "did:plc:") { 558 - ustr = fmt.Sprintf("https://plc.directory/%s", did) 559 - } else if strings.HasPrefix(did, "did:web:") { 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 - } 564 - 565 - req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil) 566 - if err != nil { 567 - return "", err 568 - } 569 - 570 - resp, err := http.DefaultClient.Do(req) 571 - if err != nil { 572 - return "", err 573 - } 574 - defer resp.Body.Close() 575 - 576 - if resp.StatusCode != 200 { 577 - io.Copy(io.Discard, resp.Body) 578 - return "", fmt.Errorf("could not find identity in plc registry") 579 - } 580 - 581 - var identity Identity 582 - if err := json.NewDecoder(resp.Body).Decode(&identity); err != nil { 583 - return "", err 584 - } 585 - 586 - var service string 587 - for _, svc := range identity.Service { 588 - if svc.ID == "#atproto_pds" { 589 - service = svc.ServiceEndpoint 590 - } 591 - } 592 - 593 - if service == "" { 594 - return "", fmt.Errorf("could not find atproto_pds service in identity services") 595 - } 596 - 597 - return service, nil 598 221 } 599 222 600 223 func getFilePath(file string) string {
+125
cmd/client_test/resolution.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + "io" 8 + "net" 9 + "net/http" 10 + "strings" 11 + 12 + "github.com/bluesky-social/indigo/atproto/syntax" 13 + ) 14 + 15 + func resolveHandle(ctx context.Context, handle string) (string, error) { 16 + var did string 17 + 18 + _, err := syntax.ParseHandle(handle) 19 + if err != nil { 20 + return "", err 21 + } 22 + 23 + recs, err := net.LookupTXT(fmt.Sprintf("_atproto.%s", handle)) 24 + if err != nil { 25 + return "", err 26 + } 27 + 28 + for _, rec := range recs { 29 + if strings.HasPrefix(rec, "did=") { 30 + did = strings.Split(rec, "did=")[1] 31 + break 32 + } 33 + } 34 + 35 + if did == "" { 36 + req, err := http.NewRequestWithContext( 37 + ctx, 38 + "GET", 39 + fmt.Sprintf("https://%s/.well-known/atproto-did", handle), 40 + nil, 41 + ) 42 + if err != nil { 43 + return "", err 44 + } 45 + 46 + resp, err := http.DefaultClient.Do(req) 47 + if err != nil { 48 + return "", err 49 + } 50 + defer resp.Body.Close() 51 + 52 + if resp.StatusCode != http.StatusOK { 53 + io.Copy(io.Discard, resp.Body) 54 + return "", fmt.Errorf("unable to resolve handle") 55 + } 56 + 57 + b, err := io.ReadAll(resp.Body) 58 + if err != nil { 59 + return "", err 60 + } 61 + 62 + maybeDid := string(b) 63 + 64 + if _, err := syntax.ParseDID(maybeDid); err != nil { 65 + return "", fmt.Errorf("unable to resolve handle") 66 + } 67 + 68 + did = maybeDid 69 + } 70 + 71 + return did, nil 72 + } 73 + 74 + func resolveService(ctx context.Context, did string) (string, error) { 75 + type Identity struct { 76 + Service []struct { 77 + ID string `json:"id"` 78 + Type string `json:"type"` 79 + ServiceEndpoint string `json:"serviceEndpoint"` 80 + } `json:"service"` 81 + } 82 + 83 + var ustr string 84 + if strings.HasPrefix(did, "did:plc:") { 85 + ustr = fmt.Sprintf("https://plc.directory/%s", did) 86 + } else if strings.HasPrefix(did, "did:web:") { 87 + ustr = fmt.Sprintf("https://%s/.well-known/did.json", did) 88 + } else { 89 + return "", fmt.Errorf("did was not a supported did type") 90 + } 91 + 92 + req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil) 93 + if err != nil { 94 + return "", err 95 + } 96 + 97 + resp, err := http.DefaultClient.Do(req) 98 + if err != nil { 99 + return "", err 100 + } 101 + defer resp.Body.Close() 102 + 103 + if resp.StatusCode != 200 { 104 + io.Copy(io.Discard, resp.Body) 105 + return "", fmt.Errorf("could not find identity in plc registry") 106 + } 107 + 108 + var identity Identity 109 + if err := json.NewDecoder(resp.Body).Decode(&identity); err != nil { 110 + return "", err 111 + } 112 + 113 + var service string 114 + for _, svc := range identity.Service { 115 + if svc.ID == "#atproto_pds" { 116 + service = svc.ServiceEndpoint 117 + } 118 + } 119 + 120 + if service == "" { 121 + return "", fmt.Errorf("could not find atproto_pds service in identity services") 122 + } 123 + 124 + return service, nil 125 + }
-1
oauth.go
··· 254 254 return nil, err 255 255 } 256 256 257 - // TODO: ?? 258 257 dpopAuthserverNonce := "" 259 258 dpopProof, err := c.AuthServerDpopJwt("POST", parUrl, dpopAuthserverNonce, dpopPrivateKey) 260 259 if err != nil {
+9 -9
xrpc.go
··· 27 27 OnDPoPNonceChanged func(did, newNonce string) 28 28 } 29 29 30 + type XrpcAuthedRequestArgs struct { 31 + Did string 32 + PdsUrl string 33 + Issuer string 34 + AccessToken string 35 + DpopPdsNonce string 36 + DpopPrivateJwk jwk.Key 37 + } 38 + 30 39 func (c *XrpcClient) getClient() *http.Client { 31 40 if c.Client == nil { 32 41 return util.RobustHTTPClient() ··· 122 131 } 123 132 124 133 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 134 } 135 135 136 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 {