this repo has no description
0
fork

Configure Feed

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

update var capitalization and expand doc comments

+38 -20
+25 -10
atproto/auth/oauth/oauth.go
··· 19 19 "github.com/google/go-querystring/query" 20 20 ) 21 21 22 - var JWT_EXPIRATION_DURATION = 30 * time.Second 22 + var jwtExpirationDuration = 30 * time.Second 23 23 24 24 // Service-level client. Used to establish and refrsh OAuth sessions, but is not itself account or session specific, and can not be used directly to make API calls on behalf of a user. 25 25 type ClientApp struct { ··· 30 30 Store ClientAuthStore 31 31 } 32 32 33 + // App-level client configuration data. 34 + // 35 + // Not to be confused with the [ClientMetadata] struct type, which represents a full client metadata JSON document. 33 36 type ClientConfig struct { 34 - ClientID string 37 + // Full client identifier, which should be an HTTP URL 38 + ClientID string 39 + // Fully qualified callback URL 35 40 CallbackURL string 36 - // set of scope strings; must include "atproto" 37 - Scopes []string 38 - 41 + // Set of OAuth scope strings, which will be both declared in client metadata document and requested for every session. Must include "atproto". 42 + Scopes []string 39 43 UserAgent string 40 44 41 45 // For confidential clients, the private client assertion key. Note that while an interface is used here, only P-256 is allowed by the current specification. ··· 45 49 KeyID *string 46 50 } 47 51 52 + // Constructs a [ClientApp] based on configuration. 48 53 func NewClientApp(config *ClientConfig, store ClientAuthStore) *ClientApp { 49 54 app := &ClientApp{ 50 55 Client: http.DefaultClient, ··· 102 107 return c 103 108 } 104 109 110 + // Whether this is a "confidential" OAuth client (with configured client attestation key), versus "public" client. 105 111 func (config *ClientConfig) IsConfidential() bool { 106 112 return config.PrivateKey != nil && config.KeyID != nil 107 113 } ··· 151 157 return strings.Join(scopes, " ") 152 158 } 153 159 154 - // Returns a ClientMetadata struct with the required fields populated based on this client configuration. Clients may want to populate additional metadata fields on top of this response. 160 + // Returns a [ClientMetadata] struct with the required fields populated based on this client configuration. Clients may want to populate additional metadata fields on top of this response. 155 161 // 156 162 // NOTE: confidential clients currently must provide JWKSUri after the fact 157 163 func (config *ClientConfig) ClientMetadata() ClientMetadata { ··· 177 183 return m 178 184 } 179 185 186 + // High-level helper for fetching session data from store, based on account DID and session identifier. 180 187 func (app *ClientApp) ResumeSession(ctx context.Context, did syntax.DID, sessionID string) (*ClientSession, error) { 181 188 182 189 sd, err := app.Store.GetSession(ctx, did, sessionID) ··· 228 235 Nonce *string `json:"nonce,omitempty"` 229 236 } 230 237 238 + // Low-level helper to generate and sign an OAuth confidential client assertion token (JWT). 231 239 func (cfg *ClientConfig) NewClientAssertion(authURL string) (string, error) { 232 240 if !cfg.IsConfidential() { 233 241 return "", fmt.Errorf("non-confidential client") ··· 263 271 RegisteredClaims: jwt.RegisteredClaims{ 264 272 ID: secureRandomBase64(16), 265 273 IssuedAt: jwt.NewNumericDate(time.Now()), 266 - ExpiresAt: jwt.NewNumericDate(time.Now().Add(JWT_EXPIRATION_DURATION)), 274 + ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtExpirationDuration)), 267 275 }, 268 276 } 269 277 if dpopNonce != "" { ··· 303 311 return fmt.Sprintf("%s", errResp["error"]) 304 312 } 305 313 306 - // Sends PAR request to auth server 314 + // Low-level helper to send PAR request to auth server, which involves starting PKCE and DPoP. 307 315 func (app *ClientApp) SendAuthRequest(ctx context.Context, authMeta *AuthServerMetadata, scope, loginHint string) (*AuthRequestData, error) { 308 316 309 317 parURL := authMeta.PushedAuthorizationRequestEndpoint ··· 330 338 if err != nil { 331 339 return nil, err 332 340 } 333 - body.ClientAssertionType = CLIENT_ASSERTION_JWT_BEARER 341 + body.ClientAssertionType = ClientAssertionJWTBearer 334 342 body.ClientAssertion = assertionJWT 335 343 } 336 344 ··· 416 424 return &parInfo, nil 417 425 } 418 426 427 + // Lower-level helper. This is usually invoked as part of [ProcessCallback]. 419 428 func (app *ClientApp) SendInitialTokenRequest(ctx context.Context, authCode string, info AuthRequestData) (*TokenResponse, error) { 420 429 421 430 // TODO: don't re-fetch? caching? ··· 437 446 if err != nil { 438 447 return nil, err 439 448 } 440 - body.ClientAssertionType = &CLIENT_ASSERTION_JWT_BEARER 449 + body.ClientAssertionType = &ClientAssertionJWTBearer 441 450 body.ClientAssertion = &clientAssertion 442 451 } 443 452 ··· 509 518 return &tokenResp, nil 510 519 } 511 520 521 + // High-level helper for starting a new session. Resolves identifier to resource server and auth server metadata, sends PAR request, persists request info to store, and returns a redirect URL. 522 + // 523 + // The `identifier` argument can be an atproto account identifier (handle or DID), or can be a URL to the account's auth server. 524 + // 525 + // The returned sting will be a web URL that the user should be redirected to (in browser) to approve the auth flow. 512 526 func (app *ClientApp) StartAuthFlow(ctx context.Context, identifier string) (string, error) { 513 527 514 528 var authserverURL string ··· 568 582 return redirectURL, nil 569 583 } 570 584 585 + // High-level helper for completing auth flow: verifies callback query parameters against persisted auth request info, makes initial token request to the auth server, validates account identifier, and persists session data. 571 586 func (app *ClientApp) ProcessCallback(ctx context.Context, params url.Values) (*ClientSessionData, error) { 572 587 573 588 state := params.Get("state")
+3 -4
atproto/auth/oauth/resolver.go
··· 13 13 14 14 // Helper for resolving OAuth documents from the public web: client metadata, auth server metadata, etc. 15 15 // 16 - // NOTE: will probably want to add flexible caching to this interface, and that may mean turning it in to an interface. 16 + // NOTE: configurable caching will likely be added in the future, but is not implemented yet. This struct may become an interface to support more flexible caching and resolution policies. 17 17 type Resolver struct { 18 18 Client *http.Client 19 19 UserAgent string ··· 83 83 return authURL, nil 84 84 } 85 85 86 - // Resolves an Auth Server URL to server metadata. 87 - // 88 - // Validates the auth server metadata before returning. 86 + // Resolves an Auth Server URL to server metadata. Validates metadata before returning. 89 87 func (r *Resolver) ResolveAuthServerMetadata(ctx context.Context, serverURL string) (*AuthServerMetadata, error) { 90 88 u, err := url.Parse(serverURL) 91 89 if err != nil { ··· 129 127 return &body, nil 130 128 } 131 129 130 + // Fetches and validates OAuth client metadata document based on identifier in URL format. 132 131 func (r *Resolver) ResolveClientMetadata(ctx context.Context, clientID string) (*ClientMetadata, error) { 133 132 u, err := url.Parse(clientID) 134 133 if err != nil {
+6 -2
atproto/auth/oauth/session.go
··· 57 57 // TODO: also persist access token creation time / expiration time? In context that token might not be an easily parsed JWT 58 58 } 59 59 60 + // Implementation of [client.AuthMethod] for an OAuth session. Handles DPoP request token signing and nonce rotation, and token refresh requests. Optionally uses a callback to persist updated session data. 61 + // 62 + // A single ClientSession instance can be called concurrently: updates to session data (the 'Data' field) are protected with a RW mutex lock. Note that concurrent calls to distinct ClientSession instances for the same session could result in clobbered session data. 60 63 type ClientSession struct { 61 64 // HTTP client used for token refresh requests 62 65 Client *http.Client ··· 90 93 if err != nil { 91 94 return "", err 92 95 } 93 - body.ClientAssertionType = &CLIENT_ASSERTION_JWT_BEARER 96 + body.ClientAssertionType = &ClientAssertionJWTBearer 94 97 body.ClientAssertion = &clientAssertion 95 98 } 96 99 ··· 180 183 Issuer: sess.Data.AuthServerURL, 181 184 ID: secureRandomBase64(16), 182 185 IssuedAt: jwt.NewNumericDate(time.Now()), 183 - ExpiresAt: jwt.NewNumericDate(time.Now().Add(JWT_EXPIRATION_DURATION)), 186 + ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtExpirationDuration)), 184 187 }, 185 188 } 186 189 if sess.Data.DPoPHostNonce != "" { ··· 335 338 return nil, fmt.Errorf("OAuth client ran out of request retries") 336 339 } 337 340 341 + // Creates a new [client.APIClient] which wraps this session for auth. 338 342 func (sess *ClientSession) APIClient() *client.APIClient { 339 343 c := client.APIClient{ 340 344 Client: sess.Client,
+4 -4
atproto/auth/oauth/types.go
··· 11 11 "github.com/bluesky-social/indigo/atproto/syntax" 12 12 ) 13 13 14 - var CLIENT_ASSERTION_JWT_BEARER = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" 14 + var ClientAssertionJWTBearer string = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" 15 15 16 16 var ( 17 17 ErrInvalidAuthServerMetadata = errors.New("invalid auth server metadata") ··· 62 62 JWKS *JWKS `json:"jwks,omitempty"` 63 63 64 64 // URL pointing to a JWKS JSON object. See `jwks` above for details. 65 - JWKSUri *string `json:"jwks_uri,omitempty"` 65 + JWKSURI *string `json:"jwks_uri,omitempty"` 66 66 67 67 // human-readable name of the client 68 68 ClientName *string `json:"client_name,omitempty"` ··· 82 82 83 83 // returns 'true' if client metadata indicates that this is a confidential client 84 84 func (m *ClientMetadata) IsConfidential() bool { 85 - if (m.JWKSUri != nil || (m.JWKS != nil && len(m.JWKS.Keys) > 0)) && m.TokenEndpointAuthMethod == "private_key_jwt" { 85 + if (m.JWKSURI != nil || (m.JWKS != nil && len(m.JWKS.Keys) > 0)) && m.TokenEndpointAuthMethod == "private_key_jwt" { 86 86 return true 87 87 } 88 88 ··· 142 142 return fmt.Errorf("%w: dpop_bound_access_tokens must be true (DPoP is required)", ErrInvalidClientMetadata) 143 143 } 144 144 145 - if m.JWKSUri != nil && *m.JWKSUri == "" { 145 + if m.JWKSURI != nil && *m.JWKSURI == "" { 146 146 return fmt.Errorf("%w: jwks_uri must be valid URL (when provided)", ErrInvalidClientMetadata) 147 147 } 148 148