this repo has no description
0
fork

Configure Feed

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

public client support

+49 -36
+32 -25
atproto/auth/oauth/oauth.go
··· 79 79 // 80 80 // If the client does not have any keys (eg, public client), returns an empty set. 81 81 func (config *ClientConfig) PublicJWKS() JWKS { 82 + 83 + jwks := JWKS{Keys: []crypto.JWK{}} 84 + 82 85 // public client with no keys 83 86 if config.PrivateKey == nil || config.KeyID == nil { 84 - return JWKS{} 87 + return jwks 85 88 } 86 89 87 90 pub, err := config.PrivateKey.PublicKey() 88 91 if err != nil { 89 - return JWKS{} 92 + return jwks 90 93 } 91 94 jwk, err := pub.JWK() 92 95 if err != nil { 93 - return JWKS{} 96 + return jwks 94 97 } 95 98 jwk.KeyID = config.KeyID 96 99 97 - jwks := JWKS{ 98 - Keys: []crypto.JWK{*jwk}, 99 - } 100 + jwks.Keys = []crypto.JWK{*jwk} 100 101 return jwks 101 102 } 102 103 103 104 // 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. 104 105 // 105 - // TODO: confidential clients currently must provide JWKSUri after the fact 106 + // NOTE: confidential clients currently must provide JWKSUri after the fact 106 107 func (config *ClientConfig) ClientMetadata(scope string) ClientMetadata { 107 108 if scope == "" { 108 109 scope = "atproto" ··· 115 116 ResponseTypes: []string{"code"}, 116 117 RedirectURIs: []string{config.CallbackURL}, 117 118 DpopBoundAccessTokens: true, 119 + TokenEndpointAuthMethod: strPtr("none"), 118 120 } 119 121 if config.IsConfidential() { 120 122 m.TokenEndpointAuthMethod = strPtr("private_key_jwt") 121 123 m.TokenEndpointAuthSigningAlg = strPtr("ES256") // XXX 122 - // TODO: what is the correct format for in-line JWKS? 123 - //m.JWKS = config.JWKS() 124 + jwks := config.PublicJWKS() 125 + m.JWKS = &jwks 124 126 } 125 127 return m 126 128 } ··· 241 243 // generate PKCE code challenge for use in PAR request 242 244 codeChallenge := S256CodeChallenge(pkceVerifier) 243 245 244 - // self-signed JWT using private key in client metadata (confidential client) 245 - // TODO: make "confidential client" mode optional 246 - assertionJWT, err := app.Config.NewClientAssertion(authMeta.Issuer) 247 - if err != nil { 248 - return nil, err 249 - } 250 - 251 246 body := PushedAuthRequest{ 252 247 ClientID: app.Config.ClientID, 253 248 State: state, 254 249 RedirectURI: app.Config.CallbackURL, 255 250 Scope: scope, 256 251 ResponseType: "code", 257 - ClientAssertionType: CLIENT_ASSERTION_JWT_BEARER, 258 - ClientAssertion: assertionJWT, 259 252 CodeChallenge: codeChallenge, 260 253 CodeChallengeMethod: "S256", 261 254 } 255 + 256 + if app.Config.IsConfidential() { 257 + // self-signed JWT using private key in client metadata (confidential client) 258 + assertionJWT, err := app.Config.NewClientAssertion(authMeta.Issuer) 259 + if err != nil { 260 + return nil, err 261 + } 262 + body.ClientAssertionType = CLIENT_ASSERTION_JWT_BEARER 263 + body.ClientAssertion = assertionJWT 264 + } 265 + 262 266 if loginHint != "" { 263 267 body.LoginHint = &loginHint 264 268 } ··· 347 351 348 352 func (app *ClientApp) SendInitialTokenRequest(ctx context.Context, authCode string, info AuthRequestData) (*TokenResponse, error) { 349 353 350 - clientAssertion, err := app.Config.NewClientAssertion(info.AuthServerURL) 351 - if err != nil { 352 - return nil, err 353 - } 354 - 355 354 // TODO: don't re-fetch? caching? 356 355 authServerMeta, err := app.Resolver.ResolveAuthServerMetadata(ctx, info.AuthServerURL) 357 356 if err != nil { ··· 364 363 GrantType: "authorization_code", 365 364 Code: authCode, 366 365 CodeVerifier: info.PKCEVerifier, 367 - ClientAssertionType: &CLIENT_ASSERTION_JWT_BEARER, 368 - ClientAssertion: &clientAssertion, 366 + } 367 + 368 + if app.Config.IsConfidential() { 369 + clientAssertion, err := app.Config.NewClientAssertion(info.AuthServerURL) 370 + if err != nil { 371 + return nil, err 372 + } 373 + body.ClientAssertionType = &CLIENT_ASSERTION_JWT_BEARER 374 + body.ClientAssertion = &clientAssertion 369 375 } 370 376 371 377 dpopPrivKey, err := crypto.ParsePrivateMultibase(info.DpopPrivateKeyMultibase) ··· 471 477 callbackURL.Path = "/oauth/callback" 472 478 app.Config.CallbackURL = callbackURL.String() 473 479 480 + // XXX: from config 474 481 scope := "atproto transition:generic" 475 482 info, err := app.SendAuthRequest(ctx, authserverMeta, username, scope) 476 483 if err != nil {
+10 -8
atproto/auth/oauth/session.go
··· 70 70 71 71 func (sess *ClientSession) RefreshTokens(ctx context.Context) error { 72 72 73 - // TODO: assuming confidential client 74 - clientAssertion, err := sess.Config.NewClientAssertion(sess.Data.AuthServerURL) 75 - if err != nil { 76 - return err 77 - } 78 - 79 73 body := RefreshTokenRequest{ 80 74 ClientID: sess.Config.ClientID, 81 75 GrantType: "authorization_code", 82 76 RefreshToken: sess.Data.RefreshToken, 83 - ClientAssertionType: &CLIENT_ASSERTION_JWT_BEARER, 84 - ClientAssertion: &clientAssertion, 77 + } 78 + 79 + if sess.Config.IsConfidential() { 80 + clientAssertion, err := sess.Config.NewClientAssertion(sess.Data.AuthServerURL) 81 + if err != nil { 82 + return err 83 + } 84 + body.ClientAssertionType = &CLIENT_ASSERTION_JWT_BEARER 85 + body.ClientAssertion = &clientAssertion 85 86 } 86 87 87 88 vals, err := query.Values(body) ··· 291 292 AccountDID: &sess.Data.AccountDID, 292 293 } 293 294 if sess.Config.UserAgent != "" { 295 + c.Headers = make(map[string][]string) 294 296 c.Headers.Set("User-Agent", sess.Config.UserAgent) 295 297 } 296 298 return &c
+7 -3
atproto/auth/oauth/types.go
··· 48 48 // At least one redirect URI is required. 49 49 RedirectURIs []string `json:"redirect_uris"` 50 50 51 - // confidential clients must set this to `private_key_jwt` 51 + // confidential clients must set this to `private_key_jwt`; public must be `none` 52 + // TODO: should this be string not *string? 52 53 TokenEndpointAuthMethod *string `json:"token_endpoint_auth_method,omitempty"` 53 54 54 55 // `none` is never allowed here. The current recommended and most-supported algorithm is ES256, but this may evolve over time. ··· 58 59 DpopBoundAccessTokens bool `json:"dpop_bound_access_tokens"` 59 60 60 61 // confidential clients must supply at least one public key in JWK format for use with JWT client authentication. Either this field or the `jwks_uri` field must be provided for confidential clients, but not both. 61 - JWKS []crypto.JWK `json:"jwks,omitempty"` 62 + JWKS *JWKS `json:"jwks,omitempty"` 62 63 63 64 // URL pointing to a JWKS JSON object. See `jwks` above for details. 64 65 JWKSUri *string `json:"jwks_uri,omitempty"` ··· 81 82 82 83 // returns 'true' if client metadata indicates that this is a confidential client 83 84 func (m *ClientMetadata) IsConfidential() bool { 84 - if (m.JWKSUri != nil || len(m.JWKS) > 0) && (m.TokenEndpointAuthMethod != nil && *m.TokenEndpointAuthMethod == "private_key_jwt") { 85 + if (m.JWKSUri != nil || (m.JWKS != nil && len(m.JWKS.Keys) > 0)) && (m.TokenEndpointAuthMethod != nil && *m.TokenEndpointAuthMethod == "private_key_jwt") { 85 86 return true 86 87 } 87 88 ··· 277 278 278 279 // Optional account identifier (DID or handle) to help with user account login and/or account switching 279 280 LoginHint *string `url:"login_hint,omitempty"` 281 + 282 + // Optional hint to auth server of what expected auth behavior should be. Eg, 'create', 'none', 'consent', 'login', 'select_account' 283 + Prompt *string `url:"prompt,omitempty"` 280 284 281 285 // Always "code" 282 286 ResponseType string `url:"response_type"`