this repo has no description
0
fork

Configure Feed

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

add refreshing

Hailey 3e0effbc 01210f72

+174 -137
+5 -4
cmd/client_test/handle_auth.go
··· 4 4 "encoding/json" 5 5 "fmt" 6 6 "net/url" 7 + "time" 7 8 8 9 "github.com/bluesky-social/indigo/atproto/syntax" 9 10 "github.com/gorilla/sessions" ··· 160 161 e.Request().Context(), 161 162 resCode, 162 163 resIss, 163 - resIss, 164 164 oauthRequest.PkceVerifier, 165 165 oauthRequest.DpopAuthserverNonce, 166 166 jwk, ··· 171 171 172 172 // TODO: resolve if needed 173 173 174 - if initialTokenResp.Resp["scope"] != scope { 174 + if initialTokenResp.Scope != scope { 175 175 return fmt.Errorf("did not receive correct scopes from token request") 176 176 } 177 177 ··· 179 179 Did: oauthRequest.Did, 180 180 PdsUrl: oauthRequest.PdsUrl, 181 181 AuthserverIss: oauthRequest.AuthserverIss, 182 - AccessToken: initialTokenResp.Resp["access_token"].(string), 183 - RefreshToken: initialTokenResp.Resp["refresh_token"].(string), 182 + AccessToken: initialTokenResp.AccessToken, 183 + RefreshToken: initialTokenResp.RefreshToken, 184 184 DpopAuthserverNonce: initialTokenResp.DpopAuthserverNonce, 185 185 DpopPrivateJwk: oauthRequest.DpopPrivateJwk, 186 + Expiration: time.Now().Add(time.Duration(int(time.Second) * int(initialTokenResp.ExpiresIn))), 186 187 } 187 188 188 189 if err := s.db.Clauses(clause.OnConflict{
+4 -16
cmd/client_test/handle_post.go
··· 6 6 "github.com/bluesky-social/indigo/atproto/syntax" 7 7 "github.com/bluesky-social/indigo/lex/util" 8 8 "github.com/bluesky-social/indigo/xrpc" 9 - "github.com/labstack/echo-contrib/session" 10 9 "github.com/labstack/echo/v4" 11 10 ) 12 11 13 12 func (s *TestServer) handleMakePost(e echo.Context) error { 14 - sess, err := session.Get("session", e) 13 + authArgs, authed, err := s.getOauthSessionAuthArgs(e) 15 14 if err != nil { 16 15 return err 17 16 } 18 17 19 - did, ok := sess.Values["did"] 20 - if !ok { 18 + if !authed { 21 19 return e.Redirect(302, "/login") 22 20 } 23 21 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 22 post := bsky.FeedPost{ 35 23 Text: "hello from atproto golang oauth client", 36 24 CreatedAt: syntax.DatetimeNow().String(), ··· 38 26 39 27 input := atproto.RepoCreateRecord_Input{ 40 28 Collection: "app.bsky.feed.post", 41 - Repo: oauthSession.Did, 29 + Repo: authArgs.Did, 42 30 Record: &util.LexiconTypeDecoder{Val: &post}, 43 31 } 44 32 45 33 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 { 34 + if err := s.xrpcCli.Do(e.Request().Context(), authArgs, xrpc.Procedure, "application/json", "com.atproto.repo.createRecord", nil, input, &out); err != nil { 47 35 return err 48 36 } 49 37
+3 -15
cmd/client_test/handle_profile.go
··· 3 3 import ( 4 4 "github.com/bluesky-social/indigo/api/bsky" 5 5 "github.com/bluesky-social/indigo/xrpc" 6 - "github.com/labstack/echo-contrib/session" 7 6 "github.com/labstack/echo/v4" 8 7 ) 9 8 10 9 func (s *TestServer) handleProfile(e echo.Context) error { 11 - sess, err := session.Get("session", e) 10 + authArgs, authed, err := s.getOauthSessionAuthArgs(e) 12 11 if err != nil { 13 12 return err 14 13 } 15 14 16 - did, ok := sess.Values["did"] 17 - if !ok { 15 + if !authed { 18 16 return e.Redirect(302, "/login") 19 17 } 20 18 21 - var oauthSession OauthSession 22 - if err := s.db.Raw("SELECT * FROM oauth_sessions WHERE did = ?", did).Scan(&oauthSession).Error; err != nil { 23 - return err 24 - } 25 - 26 - args, err := authedReqArgsFromSession(&oauthSession) 27 - if err != nil { 28 - return err 29 - } 30 - 31 19 var out bsky.ActorDefs_ProfileViewDetailed 32 - if err := s.xrpcCli.Do(e.Request().Context(), args, xrpc.Query, "", "app.bsky.actor.getProfile", map[string]any{"actor": oauthSession.Did}, nil, &out); err != nil { 20 + if err := s.xrpcCli.Do(e.Request().Context(), authArgs, xrpc.Query, "", "app.bsky.actor.getProfile", map[string]any{"actor": authArgs.Did}, nil, &out); err != nil { 33 21 return err 34 22 } 35 23
-16
cmd/client_test/main.go
··· 205 205 return e.JSON(200, s.jwksResponse) 206 206 } 207 207 208 - func authedReqArgsFromSession(session *OauthSession) (*oauth.XrpcAuthedRequestArgs, error) { 209 - privateJwk, err := oauth.ParseKeyFromBytes([]byte(session.DpopPrivateJwk)) 210 - if err != nil { 211 - return nil, err 212 - } 213 - 214 - return &oauth.XrpcAuthedRequestArgs{ 215 - Did: session.Did, 216 - AccessToken: session.AccessToken, 217 - PdsUrl: session.PdsUrl, 218 - Issuer: session.AuthserverIss, 219 - DpopPdsNonce: session.DpopPdsNonce, 220 - DpopPrivateJwk: privateJwk, 221 - }, nil 222 - } 223 - 224 208 func getFilePath(file string) string { 225 209 return fmt.Sprintf("%s/%s", staticFilePath, file) 226 210 }
+6 -8
cmd/client_test/resolution.go
··· 21 21 } 22 22 23 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 24 + if err == nil { 25 + for _, rec := range recs { 26 + if strings.HasPrefix(rec, "did=") { 27 + did = strings.Split(rec, "did=")[1] 28 + break 29 + } 32 30 } 33 31 } 34 32
+3
cmd/client_test/types.go
··· 1 1 package main 2 2 3 + import "time" 4 + 3 5 type OauthRequest struct { 4 6 ID uint 5 7 AuthserverIss string ··· 21 23 DpopPdsNonce string 22 24 DpopAuthserverNonce string 23 25 DpopPrivateJwk string 26 + Expiration time.Time 24 27 }
+75
cmd/client_test/user.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "time" 7 + 8 + oauth "github.com/haileyok/atproto-oauth-golang" 9 + "github.com/labstack/echo-contrib/session" 10 + "github.com/labstack/echo/v4" 11 + ) 12 + 13 + func (s *TestServer) getOauthSession(ctx context.Context, did string) (*OauthSession, error) { 14 + var oauthSession OauthSession 15 + if err := s.db.Raw("SELECT * FROM oauth_sessions WHERE did = ?", did).Scan(&oauthSession).Error; err != nil { 16 + return nil, err 17 + } 18 + 19 + if oauthSession.Did == "" { 20 + return nil, fmt.Errorf("did not find session in database") 21 + } 22 + 23 + if oauthSession.Expiration.Sub(time.Now()) <= 5*time.Minute { 24 + privateJwk, err := oauth.ParseKeyFromBytes([]byte(oauthSession.DpopPrivateJwk)) 25 + if err != nil { 26 + return nil, err 27 + } 28 + 29 + resp, err := s.oauthClient.RefreshTokenRequest(ctx, oauthSession.RefreshToken, oauthSession.AuthserverIss, oauthSession.DpopAuthserverNonce, privateJwk) 30 + if err != nil { 31 + return nil, err 32 + } 33 + 34 + expiration := time.Now().Add(time.Duration(int(time.Second) * int(resp.ExpiresIn))) 35 + 36 + if err := s.db.Exec("UPDATE oauth_sessions SET access_token = ?, refresh_token = ?, dpop_authserver_nonce = ?, expiration = ? WHERE did = ?", resp.AccessToken, resp.RefreshToken, resp.DpopAuthserverNonce, expiration, oauthSession.Did).Error; err != nil { 37 + return nil, err 38 + } 39 + 40 + oauthSession.AccessToken = resp.AccessToken 41 + oauthSession.RefreshToken = resp.RefreshToken 42 + oauthSession.DpopAuthserverNonce = resp.DpopAuthserverNonce 43 + oauthSession.Expiration = expiration 44 + } 45 + 46 + return &oauthSession, nil 47 + } 48 + 49 + func (s *TestServer) getOauthSessionAuthArgs(e echo.Context) (*oauth.XrpcAuthedRequestArgs, bool, error) { 50 + sess, err := session.Get("session", e) 51 + if err != nil { 52 + return nil, false, err 53 + } 54 + 55 + did, ok := sess.Values["did"] 56 + if !ok { 57 + return nil, false, nil 58 + } 59 + 60 + oauthSession, err := s.getOauthSession(e.Request().Context(), did.(string)) 61 + 62 + privateJwk, err := oauth.ParseKeyFromBytes([]byte(oauthSession.DpopPrivateJwk)) 63 + if err != nil { 64 + return nil, false, err 65 + } 66 + 67 + return &oauth.XrpcAuthedRequestArgs{ 68 + Did: oauthSession.Did, 69 + AccessToken: oauthSession.AccessToken, 70 + PdsUrl: oauthSession.PdsUrl, 71 + Issuer: oauthSession.AuthserverIss, 72 + DpopPdsNonce: oauthSession.DpopPdsNonce, 73 + DpopPrivateJwk: privateJwk, 74 + }, true, nil 75 + }
+68 -72
oauth.go
··· 333 333 func (c *Client) InitialTokenRequest( 334 334 ctx context.Context, 335 335 code, 336 - appUrl, 337 336 authserverIss, 338 337 pkceVerifier, 339 338 dpopAuthserverNonce string, ··· 359 358 "client_assertion": {clientAssertion}, 360 359 } 361 360 362 - dpopProof, err := c.AuthServerDpopJwt( 363 - "POST", 364 - authserverMeta.TokenEndpoint, 365 - dpopAuthserverNonce, 366 - dpopPrivateJwk, 367 - ) 361 + dpopProof, err := c.AuthServerDpopJwt("POST", authserverMeta.TokenEndpoint, dpopAuthserverNonce, dpopPrivateJwk) 368 362 if err != nil { 369 363 return nil, err 370 364 } 371 365 372 - req, err := http.NewRequestWithContext( 373 - ctx, 374 - "POST", 375 - authserverMeta.TokenEndpoint, 376 - strings.NewReader(params.Encode()), 377 - ) 366 + req, err := http.NewRequestWithContext(ctx, "POST", authserverMeta.TokenEndpoint, strings.NewReader(params.Encode())) 378 367 if err != nil { 379 368 return nil, err 380 369 } ··· 388 377 } 389 378 defer resp.Body.Close() 390 379 391 - var rmap map[string]any 392 - if err := json.NewDecoder(resp.Body).Decode(&rmap); err != nil { 380 + var tokenResponse TokenResponse 381 + if err := json.NewDecoder(resp.Body).Decode(&tokenResponse); err != nil { 393 382 return nil, err 394 383 } 395 384 396 - return &TokenResponse{ 397 - DpopAuthserverNonce: dpopAuthserverNonce, 398 - Resp: rmap, 399 - }, nil 385 + tokenResponse.DpopAuthserverNonce = dpopAuthserverNonce 386 + 387 + return &tokenResponse, nil 400 388 } 401 389 402 - func (c *Client) RefreshTokenRequest(ctx context.Context, authserverUrl, refreshToken, dpopAuthserverNonce string, dpopPrivateJwk jwk.Key) (*TokenResponse, error) { 403 - authserverMeta, err := c.FetchAuthServerMetadata(ctx, authserverUrl) 404 - if err != nil { 405 - return nil, err 406 - } 390 + func (c *Client) RefreshTokenRequest( 391 + ctx context.Context, 392 + refreshToken, 393 + authserverIss, 394 + dpopAuthserverNonce string, 395 + dpopPrivateJwk jwk.Key, 396 + ) (*TokenResponse, error) { 397 + // we may need to update the dpop nonce 398 + for range 2 { 399 + authserverMeta, err := c.FetchAuthServerMetadata(ctx, authserverIss) 400 + if err != nil { 401 + return nil, err 402 + } 407 403 408 - clientAssertion, err := c.ClientAssertionJwt(authserverUrl) 409 - if err != nil { 410 - return nil, err 411 - } 404 + clientAssertion, err := c.ClientAssertionJwt(authserverIss) 405 + if err != nil { 406 + return nil, err 407 + } 412 408 413 - params := url.Values{ 414 - "client_id": {c.clientId}, 415 - "grant_type": {"refresh_token"}, 416 - "refresh_token": {refreshToken}, 417 - "client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"}, 418 - "client_assertion": {clientAssertion}, 419 - } 409 + params := url.Values{ 410 + "client_id": {c.clientId}, 411 + "grant_type": {"refresh_token"}, 412 + "refresh_token": {refreshToken}, 413 + "client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"}, 414 + "client_assertion": {clientAssertion}, 415 + } 420 416 421 - dpopProof, err := c.AuthServerDpopJwt( 422 - "POST", 423 - authserverMeta.TokenEndpoint, 424 - dpopAuthserverNonce, 425 - dpopPrivateJwk, 426 - ) 427 - if err != nil { 428 - return nil, err 429 - } 417 + dpopProof, err := c.AuthServerDpopJwt("POST", authserverMeta.TokenEndpoint, dpopAuthserverNonce, dpopPrivateJwk) 418 + if err != nil { 419 + return nil, err 420 + } 430 421 431 - req, err := http.NewRequestWithContext( 432 - ctx, 433 - "POST", 434 - authserverMeta.TokenEndpoint, 435 - strings.NewReader(params.Encode()), 436 - ) 437 - if err != nil { 438 - return nil, err 439 - } 422 + req, err := http.NewRequestWithContext(ctx, "POST", authserverMeta.TokenEndpoint, strings.NewReader(params.Encode())) 423 + if err != nil { 424 + return nil, err 425 + } 440 426 441 - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 442 - req.Header.Set("DPoP", dpopProof) 427 + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 428 + req.Header.Set("DPoP", dpopProof) 443 429 444 - resp, err := c.h.Do(req) 445 - if err != nil { 446 - return nil, err 447 - } 448 - defer resp.Body.Close() 430 + resp, err := c.h.Do(req) 431 + if err != nil { 432 + return nil, err 433 + } 434 + defer resp.Body.Close() 449 435 450 - // TODO: handle same thing as above... 436 + if resp.StatusCode != 200 && resp.StatusCode != 201 { 437 + var respMap map[string]string 438 + if err := json.NewDecoder(resp.Body).Decode(&respMap); err != nil { 439 + return nil, err 440 + } 441 + 442 + if resp.StatusCode == 400 && respMap["error"] == "use_dpop_nonce" { 443 + dpopAuthserverNonce = resp.Header.Get("DPoP-Nonce") 444 + continue 445 + } 446 + 447 + return nil, fmt.Errorf("token refresh error: %s", respMap["error"]) 448 + } 449 + 450 + var tokenResponse TokenResponse 451 + if err := json.NewDecoder(resp.Body).Decode(&tokenResponse); err != nil { 452 + return nil, err 453 + } 451 454 452 - if resp.StatusCode != 200 && resp.StatusCode != 201 { 453 - b, _ := io.ReadAll(resp.Body) 454 - return nil, fmt.Errorf("token refresh error: %s", string(b)) 455 - } 455 + // set the nonce so that updates are reflected in response 456 + tokenResponse.DpopAuthserverNonce = dpopAuthserverNonce 456 457 457 - var rmap map[string]any 458 - if err := json.NewDecoder(resp.Body).Decode(&rmap); err != nil { 459 - return nil, err 458 + return &tokenResponse, nil 460 459 } 461 460 462 - return &TokenResponse{ 463 - DpopAuthserverNonce: dpopAuthserverNonce, 464 - Resp: rmap, 465 - }, nil 461 + return nil, nil 466 462 } 467 463 468 464 func generateToken(len int) (string, error) {
+7 -2
types.go
··· 8 8 ) 9 9 10 10 type TokenResponse struct { 11 - DpopAuthserverNonce string 12 - Resp map[string]any 11 + DpopAuthserverNonce string `json:"-"` 12 + AccessToken string `json:"access_token"` 13 + RefreshToken string `json:"refresh_token"` 14 + ExpiresIn int `json:"expires_in"` 15 + Scope string `json:"scope"` 16 + Sub string `json:"sub"` 17 + TokenType string `json:"token_type"` 13 18 } 14 19 15 20 type RefreshTokenArgs struct {
+3 -4
xrpc.go
··· 211 211 212 212 // if we get a new nonce, update the nonce and make the request again 213 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 214 + authedArgs.DpopPdsNonce = resp.Header.Get("DPoP-Nonce") 215 + c.OnDPoPNonceChanged(authedArgs.Did, authedArgs.DpopPdsNonce) 217 216 continue 218 217 } 219 218 ··· 240 239 } 241 240 } 242 241 243 - break 242 + return nil 244 243 } 245 244 246 245 return nil