A community based topic aggregation platform built on atproto

fix(auth): use refresh token for PDS session refresh

The Indigo xrpc client always sends Auth.AccessJwt as the Authorization
header, but com.atproto.server.refreshSession requires the refresh token
in that header. This was causing "InvalidToken: Invalid token type" errors
when community tokens needed refreshing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+8 -6
+1 -1
internal/core/communities/service.go
··· 494 494 log.Printf("[TOKEN-REFRESH] Community: %s, Event: token_refresh_started, Message: Access token expiring soon", fresh.DID) 495 495 496 496 // Attempt token refresh using refresh token 497 - newAccessToken, newRefreshToken, err := refreshPDSToken(ctx, fresh.PDSURL, fresh.PDSAccessToken, fresh.PDSRefreshToken) 497 + newAccessToken, newRefreshToken, err := refreshPDSToken(ctx, fresh.PDSURL, fresh.PDSRefreshToken) 498 498 if err != nil { 499 499 // Check if refresh token expired (need password fallback) 500 500 // Match both "ExpiredToken" and "Token has expired" error messages
+7 -5
internal/core/communities/token_refresh.go
··· 13 13 // refreshPDSToken exchanges a refresh token for new access and refresh tokens 14 14 // Uses com.atproto.server.refreshSession endpoint via Indigo SDK 15 15 // CRITICAL: Refresh tokens are single-use - old refresh token is revoked on success 16 - func refreshPDSToken(ctx context.Context, pdsURL, currentAccessToken, refreshToken string) (newAccessToken, newRefreshToken string, err error) { 16 + func refreshPDSToken(ctx context.Context, pdsURL, refreshToken string) (newAccessToken, newRefreshToken string, err error) { 17 17 if pdsURL == "" { 18 18 return "", "", fmt.Errorf("PDS URL is required") 19 19 } ··· 21 21 return "", "", fmt.Errorf("refresh token is required") 22 22 } 23 23 24 - // Create XRPC client with auth credentials 25 - // The refresh endpoint requires authentication with the refresh token 24 + // Create XRPC client with refresh token as the auth credential 25 + // IMPORTANT: The xrpc client always sends AccessJwt as the Authorization header, 26 + // but refreshSession requires the refresh token in that header. 27 + // So we put the refresh token in AccessJwt to make it work correctly. 26 28 client := &xrpc.Client{ 27 29 Host: pdsURL, 28 30 Auth: &xrpc.AuthInfo{ 29 - AccessJwt: currentAccessToken, // Can be expired (not used for refresh auth) 30 - RefreshJwt: refreshToken, // This is what authenticates the refresh request 31 + AccessJwt: refreshToken, // Refresh token goes here (sent as Authorization header) 32 + RefreshJwt: refreshToken, // Also set here for completeness 31 33 }, 32 34 } 33 35