A community based topic aggregation platform built on atproto
1package pds 2 3import ( 4 "context" 5 "fmt" 6 "net/http" 7 8 "github.com/bluesky-social/indigo/atproto/atclient" 9 "github.com/bluesky-social/indigo/atproto/auth/oauth" 10 "github.com/bluesky-social/indigo/atproto/syntax" 11) 12 13// NewFromOAuthSession creates a PDS client from an OAuth session. 14// This uses DPoP authentication - the correct method for OAuth tokens. 15// 16// The oauthClient is used to resume the session and get a properly configured 17// APIClient that handles DPoP proof generation and nonce rotation automatically. 18func NewFromOAuthSession(ctx context.Context, oauthClient *oauth.ClientApp, sessionData *oauth.ClientSessionData) (Client, error) { 19 if oauthClient == nil { 20 return nil, fmt.Errorf("oauthClient is required") 21 } 22 if sessionData == nil { 23 return nil, fmt.Errorf("sessionData is required") 24 } 25 26 // ResumeSession reconstructs the OAuth session with DPoP key 27 // and returns a ClientSession that can generate authenticated requests 28 sess, err := oauthClient.ResumeSession(ctx, sessionData.AccountDID, sessionData.SessionID) 29 if err != nil { 30 return nil, fmt.Errorf("failed to resume OAuth session: %w", err) 31 } 32 33 // APIClient() returns an *atclient.APIClient configured with DPoP auth 34 apiClient := sess.APIClient() 35 36 return &client{ 37 apiClient: apiClient, 38 did: sessionData.AccountDID.String(), 39 host: sessionData.HostURL, 40 }, nil 41} 42 43// NewFromPasswordAuth creates a PDS client using password authentication. 44// This uses Bearer token authentication from com.atproto.server.createSession. 45// 46// Primarily used for: 47// - E2E tests with local PDS 48// - Development/debugging tools 49// - Non-OAuth clients 50// 51// Note: This establishes a new session with the PDS. For repeated calls, 52// consider using NewFromAccessToken if you already have a valid access token. 53func NewFromPasswordAuth(ctx context.Context, host, handle, password string) (Client, error) { 54 if host == "" { 55 return nil, fmt.Errorf("host is required") 56 } 57 if handle == "" { 58 return nil, fmt.Errorf("handle is required") 59 } 60 if password == "" { 61 return nil, fmt.Errorf("password is required") 62 } 63 64 // LoginWithPasswordHost creates a session and returns an authenticated APIClient 65 // This handles the createSession call and Bearer token setup 66 apiClient, err := atclient.LoginWithPasswordHost(ctx, host, handle, password, "", nil) 67 if err != nil { 68 return nil, fmt.Errorf("failed to login with password: %w", err) 69 } 70 71 // Get DID from the authenticated client 72 did := "" 73 if apiClient.AccountDID != nil { 74 did = apiClient.AccountDID.String() 75 } 76 77 return &client{ 78 apiClient: apiClient, 79 did: did, 80 host: host, 81 }, nil 82} 83 84// NewFromAccessToken creates a PDS client from an existing access token. 85// This is useful when you already have a valid Bearer token (e.g., from createSession) 86// and don't want to re-authenticate. 87// 88// WARNING: This creates a client with Bearer auth only. Do NOT use this with 89// OAuth access tokens - those require DPoP proofs. Use NewFromOAuthSession instead. 90func NewFromAccessToken(host, did, accessToken string) (Client, error) { 91 if host == "" { 92 return nil, fmt.Errorf("host is required") 93 } 94 if did == "" { 95 return nil, fmt.Errorf("did is required") 96 } 97 if accessToken == "" { 98 return nil, fmt.Errorf("accessToken is required") 99 } 100 101 // Create APIClient with Bearer auth 102 apiClient := atclient.NewAPIClient(host) 103 apiClient.Auth = &bearerAuth{token: accessToken} 104 105 return &client{ 106 apiClient: apiClient, 107 did: did, 108 host: host, 109 }, nil 110} 111 112// bearerAuth implements atclient.AuthMethod for simple Bearer token auth. 113// This is used for password-based sessions where DPoP is not required. 114type bearerAuth struct { 115 token string 116} 117 118// Ensure bearerAuth implements atclient.AuthMethod. 119var _ atclient.AuthMethod = (*bearerAuth)(nil) 120 121// DoWithAuth adds the Bearer token to the request and executes it. 122func (b *bearerAuth) DoWithAuth(c *http.Client, req *http.Request, _ syntax.NSID) (*http.Response, error) { 123 req.Header.Set("Authorization", "Bearer "+b.token) 124 return c.Do(req) 125}