Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/youtube.py (Python)

Code before:
                        data=json.dumps({
                            'client_id': self._OAUTH_CLIENT_ID,
                            'client_secret': self._OAUTH_CLIENT_SECRET,
                            'device_code': code_response['device_code'],
                            'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
                        }).encode(),
                        headers={'Content-Type': 'application/json'})
                except ExtractorError as e:
                    if isinstance(e.cause, TransportError):
                        retry.error = e
                        break
                    elif isinstance(e.cause, HTTPError):
                        token_response = self._parse_json(
                            self._webpage_read_content(e.cause.response, None, self._OAUTH_DISPLAY_ID, fatal=False) or '{}',
                            video_id=self._OAUTH_DISPLAY_ID, fatal=False)
                        if not (error := traverse_obj(token_response, 'error', {str})):
                            retry.error = e
                            break

                        if error == 'authorization_pending':
                            time.sleep(poll_interval)
                            continue
                        elif error == 'expired_token':
                            raise ExtractorError('Authorization timed out', expected=True, video_id=self._OAUTH_DISPLAY_ID)
                        elif error == 'access_denied':
                            raise ExtractorError('User denied access to account', expected=True, video_id=self._OAUTH_DISPLAY_ID)
                        elif error == 'slow_down':
                            # RFC8628 § 3.5: add 5 seconds to the poll interval
                            poll_interval += 5
                            time.sleep(poll_interval)
                            continue
                        else:
                            raise ExtractorError(
                                f'Authorization server returned an error when fetching access token: {error}',
                                video_id=self._OAUTH_DISPLAY_ID)
                    else:
                        raise

                return token_response

    def _update_oauth(self):
        token = YoutubeBaseInfoExtractor._OAUTH_ACCESS_TOKEN_CACHE.get(self._OAUTH_PROFILE)
        if token is None or token['expiry'] > time.time():
            return

        self._set_oauth_info(self._refresh_token(token['refresh_token']), self._OAUTH_PROFILE)

    def _check_login_required(self):
        if self._LOGIN_REQUIRED and not self.is_authenticated:
            self.raise_login_required('Login details are needed to download this content', method='cookies')


Review comment (security):
```suggestion
                            raise ExtractorError(
                                'You have been denied access to this account', expected=True, video_id=self._OAUTH_DISPLAY_ID)
```

Code after:
                    token_response = self._download_json(
                        self._OAUTH_TOKEN_ENDPOINT,
                        video_id=self._OAUTH_DISPLAY_ID,
                        note=False,
                        errnote='Failed to request access token',
                        data=json.dumps({
                            'client_id': self._OAUTH_CLIENT_ID,
                            'client_secret': self._OAUTH_CLIENT_SECRET,
                            'device_code': code_response['device_code'],
                            'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
                        }).encode(),
                        headers={'Content-Type': 'application/json'})
                except ExtractorError as e:
                    if isinstance(e.cause, TransportError):
                        retry.error = e
                        break
                    elif isinstance(e.cause, HTTPError):
                        error = self._read_oauth_error_response(e.cause.response)
                        if not error:
                            retry.error = e
                            break

                        if error == 'authorization_pending':
                            time.sleep(poll_interval)
                            continue
                        elif error == 'expired_token':
                            raise ExtractorError(
                                'Authorization timed out', expected=True, video_id=self._OAUTH_DISPLAY_ID)
                        elif error == 'access_denied':
                            raise ExtractorError(
                                'You denied access to an account', expected=True, video_id=self._OAUTH_DISPLAY_ID)
                        elif error == 'slow_down':
                            # RFC8628 § 3.5: add 5 seconds to the poll interval
                            poll_interval += 5
                            time.sleep(poll_interval)
                            continue
                        else:
                            raise ExtractorError(
                                f'Authorization server returned an error when fetching access token: {error}',
                                video_id=self._OAUTH_DISPLAY_ID)
                    raise

                return token_response

    def _update_oauth(self):
        token = YoutubeBaseInfoExtractor._OAUTH_ACCESS_TOKEN_CACHE.get(self._OAUTH_PROFILE)
        if token is None or token['expiry'] > time.time():
            return

        self._set_oauth_info(self._refresh_token(token['refresh_token']))