this repo has no description
0
fork

Configure Feed

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

oauth client: rework RevokeSession error handling, address review comments

+30 -19
+2 -2
atproto/auth/oauth/doc.go
··· 122 122 123 123 The [ClientSession] will handle nonce updates and token refreshes, and persist the results in the [ClientAuthStore]. 124 124 125 - To log out a user, delete their session from the [ClientAuthStore]: 125 + To log out a user, use the [ClientApp.Logout] helper method, which revokes their tokens (if supported by the AS) and deletes their session from the [ClientAuthStore]: 126 126 127 - if err := oauthApp.Store.DeleteSession(r.Context(), did, sessionID); err != nil { 127 + if err := oauthApp.Logout(r.Context(), did, sessionID); err != nil { 128 128 return err 129 129 } 130 130
+9 -2
atproto/auth/oauth/oauth.go
··· 668 668 return err 669 669 } 670 670 671 - // Tell the AS to revoke the tokens 672 - sess.RevokeSession(ctx) 671 + // Tell the AS to revoke the tokens, if supported 672 + if sess.Data.AuthServerRevocationEndpoint == "" { 673 + slog.Info("AS does not support token revocation, skipping RevokeSession") 674 + } else { 675 + err = sess.RevokeSession(ctx) 676 + if err != nil { 677 + slog.Warn("error during session revocation", "err", err) 678 + } 679 + } 673 680 674 681 // Delete from our own session store 675 682 err = app.Store.DeleteSession(ctx, did, sessionID)
+17 -15
atproto/auth/oauth/session.go
··· 4 4 "bytes" 5 5 "context" 6 6 "encoding/json" 7 + "errors" 7 8 "fmt" 8 9 "log/slog" 9 10 "net/http" ··· 133 134 // already updated nonce value above; loop around and try again 134 135 continue 135 136 } 136 - return nil, fmt.Errorf("request failed (HTTP %d): %s", resp.StatusCode, reason) 137 + return nil, fmt.Errorf("auth server request failed (HTTP %d): %s", resp.StatusCode, reason) 137 138 } 138 139 139 140 // otherwise process response (success or other error type) ··· 188 189 189 190 // If supported by the AS, use the revocation endpoint to revoke both the access token and the refresh token. 190 191 // This method always succeeds - any errors during revocation are logged but not returned. 191 - func (sess *ClientSession) RevokeSession(ctx context.Context) { 192 + func (sess *ClientSession) RevokeSession(ctx context.Context) error { 193 + sess.lk.Lock() 194 + defer sess.lk.Unlock() 195 + 192 196 if sess.Data.AuthServerRevocationEndpoint == "" { 193 - slog.Info("AS does not advertise token revocation support, skipping") 194 - return 197 + return fmt.Errorf("AS does not support token revocation") 195 198 } 196 199 197 - sess.lk.Lock() 198 - defer sess.lk.Unlock() 199 - 200 - resp, err := sess.postToAuthServer(ctx, sess.Data.AuthServerRevocationEndpoint, RevocationRequest{ 200 + resp, err1 := sess.postToAuthServer(ctx, sess.Data.AuthServerRevocationEndpoint, RevocationRequest{ 201 201 ClientID: sess.Config.ClientID, 202 202 Token: sess.Data.AccessToken, 203 203 TokenTypeHint: "access_token", 204 204 }) 205 - if err != nil { 206 - slog.Warn("failed revoking access token", "err", err) 205 + if err1 != nil { 206 + err1 = fmt.Errorf("failed revoking access token: %w", err1) 207 207 } else { 208 208 if resp.StatusCode != http.StatusOK { 209 - slog.Warn("bad HTTP status while revoking access token", "status_code", resp.StatusCode) 209 + err1 = fmt.Errorf("bad HTTP status while revoking access token (%d)", resp.StatusCode) 210 210 } 211 211 resp.Body.Close() 212 212 } 213 213 214 - resp, err = sess.postToAuthServer(ctx, sess.Data.AuthServerRevocationEndpoint, RevocationRequest{ 214 + resp, err2 := sess.postToAuthServer(ctx, sess.Data.AuthServerRevocationEndpoint, RevocationRequest{ 215 215 ClientID: sess.Config.ClientID, 216 216 Token: sess.Data.RefreshToken, 217 217 TokenTypeHint: "refresh_token", 218 218 }) 219 - if err != nil { 220 - slog.Warn("failed revoking refresh token", "err", err) 219 + if err2 != nil { 220 + err2 = fmt.Errorf("failed revoking refresh token: %w", err1) 221 221 } else { 222 222 if resp.StatusCode != 200 { 223 - slog.Warn("bad HTTP status while revoking refresh token", "status_code", resp.StatusCode) 223 + err2 = fmt.Errorf("bad HTTP status while revoking refresh token (%d)", resp.StatusCode) 224 224 } 225 225 resp.Body.Close() 226 226 } 227 + 228 + return errors.Join(err1, err2) // returns nil if both errors are nil 227 229 } 228 230 229 231 // Constructs and signs a DPoP JWT to include in request header to Host (aka Resource Server, aka PDS). These tokens are different from those used with Auth Server token endpoints (even if the PDS is filling both roles)
+2
atproto/auth/oauth/types.go
··· 415 415 } 416 416 417 417 // The fields which are included in a token revocation request. These HTTP POST bodies are form-encoded, so use URL encoding syntax, not JSON. 418 + // 419 + // Per https://datatracker.ietf.org/doc/html/rfc7009#section-2.1 418 420 type RevocationRequest struct { 419 421 // Client ID, aka client metadata URL 420 422 ClientID string `url:"client_id"`